Cloud-work adoption asks git once per run, not once per run per head - #1629
Merged
Conversation
The pass proved a `claude/*` branch was a run's by walking every head on origin and asking about each one separately: a `cat-file -e`, sometimes a `fetch`, then a `merge-base --is-ancestor` — per waiting run, per head. Nothing prunes those heads, so the cost grew with the repo rather than with the work. One `git fetch --prune origin '+refs/heads/claude/*:refs/remotes/origin/claude/*'` per pass now brings them local, and one `git for-each-ref --contains=<anchor>` per run asks the same ancestry question of the whole set at once — which also answers "exactly one?" in the same call, rather than by counting a loop. `--prune` is load-bearing. These refs are now a standing local copy of a list that used to be read live from origin each pass, so without it a `claude/*` branch deleted on origin would go on being matched. Correcting the ticket on one point: #1607 said the old fetch left its objects reachable only from `FETCH_HEAD`, to be re-fetched after gc. That is not true of an ordinary checkout — git opportunistically updates the remote-tracking branches its configured refspec covers even when the command line names its own, which was verified against real git both ways. It is true of a checkout cloned with a narrower refspec, so naming the destination is still right; the saving here is the call count, not the transfer. The other tests speak to a fake git, which agrees with whatever commands it is handed. A new one runs the real thing: a checkout that has never seen the session's branches picks out the one descending from its anchor and leaves the one forked before it. It was validated by inverting the ancestry query and watching it fail. Refs #1607. Its remaining note — `listAgents` parsing every archived record each pass when the id in the filename could reject most of them unread — is untouched, so the issue stays open. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
suleimansh
added a commit
that referenced
this pull request
Aug 22, 2026
…1629) The pass proved a `claude/*` branch was a run's by walking every head on origin and asking about each one separately: a `cat-file -e`, sometimes a `fetch`, then a `merge-base --is-ancestor` — per waiting run, per head. Nothing prunes those heads, so the cost grew with the repo rather than with the work. One `git fetch --prune origin '+refs/heads/claude/*:refs/remotes/origin/claude/*'` per pass now brings them local, and one `git for-each-ref --contains=<anchor>` per run asks the same ancestry question of the whole set at once — which also answers "exactly one?" in the same call, rather than by counting a loop. `--prune` is load-bearing. These refs are now a standing local copy of a list that used to be read live from origin each pass, so without it a `claude/*` branch deleted on origin would go on being matched. Correcting the ticket on one point: #1607 said the old fetch left its objects reachable only from `FETCH_HEAD`, to be re-fetched after gc. That is not true of an ordinary checkout — git opportunistically updates the remote-tracking branches its configured refspec covers even when the command line names its own, which was verified against real git both ways. It is true of a checkout cloned with a narrower refspec, so naming the destination is still right; the saving here is the call count, not the transfer. The other tests speak to a fake git, which agrees with whatever commands it is handed. A new one runs the real thing: a checkout that has never seen the session's branches picks out the one descending from its anchor and leaves the one forked before it. It was validated by inverting the ancestry query and watching it fail. Refs #1607. Its remaining note — `listAgents` parsing every archived record each pass when the id in the filename could reject most of them unread — is untouched, so the issue stays open. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The second half of #1607, and independent of #1628 (different file, so neither is stacked on the other).
To prove a
claude/*branch belongs to a run, the pass walked every head on origin and asked about each one separately: acat-file -e, sometimes afetch, then amerge-base --is-ancestor— per waiting run, per head. Nothing prunes those heads, so the cost grows with the repo rather than with the work.What changed
One
git fetch --prune origin '+refs/heads/claude/*:refs/remotes/origin/claude/*'per pass brings the heads local, and onegit for-each-ref --contains=<anchor>per run asks the same ancestry question of the whole set at once — which also answers exactly one? in the same call, instead of by counting a loop.--pruneis load-bearing rather than tidiness. These refs are now a standing local copy of a list that used to be read live from origin every pass, so without it aclaude/*branch deleted on origin would go on being matched.A correction to the ticket
#1607 (mine) claimed the old fetch left its objects reachable only from
FETCH_HEAD, to be re-fetched after gc. That is wrong for an ordinary checkout. Verified against real git both ways:remote.origin.fetchgit fetch origin refs/heads/claude/X+refs/heads/*:refs/remotes/origin/*(normal clone)refs/remotes/origin/claude/X+refs/heads/main:refs/remotes/origin/main(--single-branch)FETCH_HEADonlyGit opportunistically updates the remote-tracking branches its configured refspec covers even when the command line names its own. So the gc concern only ever applied to a narrowly-cloned checkout. Naming the destination is still correct — it makes the behaviour independent of how the checkout was cloned — but the real saving here is the call count, not the transfer, and the PR says so rather than repeating the ticket's claim.
Verification
The existing tests speak to a fake git, which will agree with whatever commands it is handed — so they cannot tell a correct invocation from a wrong one. A new test runs the real thing: a checkout that has never seen the session's branches must fetch them, pick the one descending from its anchor, and leave the one forked before it.
Getting that fixture right took two attempts, and the first one is worth recording. Version one built the branches in the same repo it then ran the pass in — and passed even with the fetch destination deliberately broken, because
git pushwrites the remote-tracking refs itself, sofor-each-refwas reading refs the fetch never had to create. It was a test of nothing. The fixture now pushes everyclaude/*branch from a separate clone standing in for the cloud VM.The rebuilt test was then validated by inverting its ancestry query (
--contains→--no-contains), which fails it along with six others. Full suite green at 1525 passed / 0 failed.Not in scope
#1607's remaining note is untouched, so the issue stays open (no closing keyword):
listAgentsstill parses every archived record each pass, when the timestamp in the filename could reject most of them unread.