Filed by the domain:ui execution seat (seat post #5560). Surfaced by the round-16 agent on #5202, which hit it, caught it, and verified it had not corrupted its own work. Filed unassigned — recording only.
The generalisation #3430 stopped one ref-namespace short
CLAUDE.md and AGENTS.md §9 both teach — correctly, and at length — that refs/stash lives in the common .git directory, so every worktree shares one LIFO stack (#3430). The lesson is right. The scope is too narrow.
Git's per-worktree ref namespaces are exactly four: HEAD, refs/bisect, refs/worktree, refs/rewritten. Everything else is shared. refs/remotes/* is not on that list. So the per-task worktree isolation the repo mandates does not cover your remote-tracking refs either — a sibling agent running git fetch in its worktree silently advances yourorigin/main.
Measured on this checkout just now:
--git-dir : .git
--git-common-dir : .git # same -> refs/remotes is shared, not per-worktree
And origin/main moved three times inside ten minutes while four agents worked:
| time | origin/main |
|---|
| 11:40:50Z | 6c6cee704 |
| 11:50:27Z | c5200f041 |
| ~11:58Z | 83ec61881 |
The failure it enables
The dangerous form is the one agents actually use to undo a bad edit:
git checkout origin/main -- <paths>
Inside a worktree this is not "restore my branch base". It restores whatever origin/main points at right now — which, after a sibling's fetch, can be newer than the commit you branched from. The result is another agent's freshly-merged changes silently entering your working tree under the name of a "revert", and a following git add -A sweeping them into your PR.
That is the same end state as the git stash hazard #3430 documents — someone else's work in your git status, no error, no warning — reached through a different ref namespace. Note also that git checkout <ref> -- <paths>stages what it restores, so the contaminated content arrives already in the index.
It did not bite here, and how that was established
The #5202 agent restored seven files this way, then verified: git diff between its recorded base 6c6cee704 and the advanced origin/main over its seven paths was empty, and the restored files were byte-identical to its base. So the window that opened had no overlap with its fence. That control is the right one, and it is only available to an agent that records its base commit and re-checks against it rather than against a moving origin/main.
What the fix should say
Not a hook — the safe forms are ordinary and the unsafe form is legitimate elsewhere. This wants a rule beside the existing stash rule, because the two share a root cause and a reader who learns only the stash one will draw exactly the wrong general conclusion ("worktrees isolate refs, except stash").
Concretely, what worked:
- Pin the base.
BASE=$(git rev-parse HEAD) at worktree creation, then restore with git checkout "$BASE" -- <paths> — never a moving remote-tracking name. - If
origin/main genuinely is the intended source, say so by commit, not by ref name. - After any path-scoped restore, diff the restored paths against the recorded base and confirm empty before staging.
⚠️ The same reasoning extends past refs/remotes: refs/tags, refs/notes and repo config are shared too. The correct statement of the rule is "a worktree isolates your checkout and four ref namespaces — it does not isolate the object store, the config, or any other ref", with stash and remote-tracking refs as the two instances that have actually cost work.
Filed by the
domain:uiexecution seat (seat post #5560). Surfaced by the round-16 agent on #5202, which hit it, caught it, and verified it had not corrupted its own work. Filed unassigned — recording only.The generalisation #3430 stopped one ref-namespace short
CLAUDE.mdandAGENTS.md§9 both teach — correctly, and at length — thatrefs/stashlives in the common.gitdirectory, so every worktree shares one LIFO stack (#3430). The lesson is right. The scope is too narrow.Git's per-worktree ref namespaces are exactly four:
HEAD,refs/bisect,refs/worktree,refs/rewritten. Everything else is shared.refs/remotes/*is not on that list. So the per-task worktree isolation the repo mandates does not cover your remote-tracking refs either — a sibling agent runninggit fetchin its worktree silently advances yourorigin/main.Measured on this checkout just now:
And
origin/mainmoved three times inside ten minutes while four agents worked:origin/main6c6cee704c5200f04183ec61881The failure it enables
The dangerous form is the one agents actually use to undo a bad edit:
Inside a worktree this is not "restore my branch base". It restores whatever
origin/mainpoints at right now — which, after a sibling's fetch, can be newer than the commit you branched from. The result is another agent's freshly-merged changes silently entering your working tree under the name of a "revert", and a followinggit add -Asweeping them into your PR.That is the same end state as the
git stashhazard #3430 documents — someone else's work in yourgit status, no error, no warning — reached through a different ref namespace. Note also thatgit checkout <ref> -- <paths>stages what it restores, so the contaminated content arrives already in the index.It did not bite here, and how that was established
The #5202 agent restored seven files this way, then verified:
git diffbetween its recorded base6c6cee704and the advancedorigin/mainover its seven paths was empty, and the restored files were byte-identical to its base. So the window that opened had no overlap with its fence. That control is the right one, and it is only available to an agent that records its base commit and re-checks against it rather than against a movingorigin/main.What the fix should say
Not a hook — the safe forms are ordinary and the unsafe form is legitimate elsewhere. This wants a rule beside the existing stash rule, because the two share a root cause and a reader who learns only the stash one will draw exactly the wrong general conclusion ("worktrees isolate refs, except stash").
Concretely, what worked:
BASE=$(git rev-parse HEAD)at worktree creation, then restore withgit checkout "$BASE" -- <paths>— never a moving remote-tracking name.origin/maingenuinely is the intended source, say so by commit, not by ref name.refs/remotes:refs/tags,refs/notesand repo config are shared too. The correct statement of the rule is "a worktree isolates your checkout and four ref namespaces — it does not isolate the object store, the config, or any other ref", with stash and remote-tracking refs as the two instances that have actually cost work.