Intent: GET …/pulls/:number stops paying for git processes on the common read.
Seen 2026-11-02: GET /api/v1/repos/trevato/forge/pulls/8144 took 17.9 s with head;dur=7017.6. head is refreshPullHead (src/core/following.ts): two resolveOid spawns (git rev-parse) on every read of a cross-repo pull, and nearly every crew pull is cross-repo. pullMergeStates already reads tips from the ref files with readTips, with no process.
Done looks like:
[ ] refreshPullHead reads the fork's branch tip and the base's refs/pull/N/head with readTips (or the same ref-file read). It spawns git only when they differ, to fetch.
[ ] Packed refs and a missing ref behave as today: a test for each, and a test that an unmoved head spawns no process (count spawns through the existing seam, or inject the reader).
[ ] The pull quotes head;dur for a cross-repo pull on this pull's preview: under 5 ms when nothing moved.
Files: src/core/following.ts, src/git/tips.ts (only if it needs a packed-refs case), a test.
Risk: none to the contract; the answer is the same oid.
Intent:
GET …/pulls/:numberstops paying for git processes on the common read.Seen 2026-11-02:
GET /api/v1/repos/trevato/forge/pulls/8144took 17.9 s withhead;dur=7017.6.headisrefreshPullHead(src/core/following.ts): tworesolveOidspawns (git rev-parse) on every read of a cross-repo pull, and nearly every crew pull is cross-repo.pullMergeStatesalready reads tips from the ref files withreadTips, with no process.Done looks like:
refreshPullHeadreads the fork's branch tip and the base'srefs/pull/N/headwithreadTips(or the same ref-file read). It spawns git only when they differ, to fetch.head;durfor a cross-repo pull on this pull's preview: under 5 ms when nothing moved.Files: src/core/following.ts, src/git/tips.ts (only if it needs a packed-refs case), a test. Risk: none to the contract; the answer is the same oid.