Skip to content

Cloud-work adoption asks git once per run, not once per run per head - #1629

Merged
suleimansh merged 1 commit into
mainfrom
cloud-adoption-one-fetch
Aug 21, 2026
Merged

Cloud-work adoption asks git once per run, not once per run per head#1629
suleimansh merged 1 commit into
mainfrom
cloud-adoption-one-fetch

Conversation

@suleimansh

Copy link
Copy Markdown
Contributor

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: a cat-file -e, sometimes a fetch, then a merge-base --is-ancestorper 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 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, instead of by counting a loop.

--prune is 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 a claude/* 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.fetch old git fetch origin refs/heads/claude/X
+refs/heads/*:refs/remotes/origin/* (normal clone) does write refs/remotes/origin/claude/X
+refs/heads/main:refs/remotes/origin/main (--single-branch) writes nothing; FETCH_HEAD only

Git 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 push writes the remote-tracking refs itself, so for-each-ref was reading refs the fetch never had to create. It was a test of nothing. The fixture now pushes every claude/* 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): listAgents still parses every archived record each pass, when the timestamp in the filename could reject most of them unread.

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
suleimansh merged commit 899b53c into main Aug 21, 2026
2 checks passed
@suleimansh
suleimansh deleted the cloud-adoption-one-fetch branch August 21, 2026 18:22
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant