Add: discipline rule on shared branch refs across worktrees - #1554
Conversation
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughAdds Git worktree discipline guidance covering safe branch creation, detached-HEAD parking, forced branch resets, cross-worktree state divergence, and verification with ChangesWorktree branch safety
Estimated code review effort: 1 (Trivial) | ~3 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.claude/rules/discipline.md:
- Around line 116-118: Update the distinction in the relevant guidance around
upstream/main and main to say that the remote-tracking ref is normally updated
by fetch, rather than claiming fetch is the only way it can be moved; preserve
the existing explanation that main is a local branch owned by a worktree.
- Around line 94-95: Update the worktree state explanation around the branch ref
discussion to explicitly include each worktree’s own HEAD/symref alongside its
index and working tree. Preserve the distinction that branch refs are shared,
while HEAD, index, and files are per-worktree, and ensure the later explanation
can rely on this distinction.
- Around line 139-145: Revise the staged-difference explanation in the “staged”
guidance to state that git status reports the net HEAD-to-index tree difference,
not every intervening commit, and that intervening commits may cancel one
another. Qualify the git commit warning so it says the commit reverses those
changes only when the index still exactly matches the old tree.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 646883da-a058-4b25-8342-15ba73853465
📒 Files selected for processing (1)
.claude/rules/discipline.md
This repo is worked through many concurrent worktrees, and a branch ref is shared by all of them — `HEAD`, the index, and the working tree are what is per worktree. So `git checkout -B <name>` does not merely move "your" branch: if another worktree has that name checked out, it is taken out from under it. Rule 6 states the general form: only ever create a new branch, or reset one you exclusively own. Park a worktree on a detached HEAD between tasks rather than on a shared branch, and read merged content with `git show upstream/main:<path>` instead of checking anything out. The rule names `-B` as the specific trap, because git already refuses the obvious forms. Both `git checkout <branch>` and `git branch -f <branch>` are rejected when another worktree holds the branch, but `git checkout -B <branch> <ref>` is not (git 2.39.1). That one call creates the dual-checkout state the other two guards exist to prevent, and afterwards every further force-move is legal, since moving a branch your own worktree has checked out is an ordinary reset. The guard never fires again. It also records what the victim sees, because the symptom does not look like a ref problem. The other worktree's HEAD is a symref, so it follows the branch to the new commit while its index and files stay at the old one. "Staged" being the HEAD-to-index diff by definition, `git status` there reports the net tree difference between the two commits, in reverse, as staged modifications — a file you added appears as a staged deletion; anything a later commit undid cancels out and never shows. So long as that index still matches the old tree, a commit made there would revert the whole net difference and look routine to whoever ran it. Written from an incident in this repo: eight such force-moves of `main` from a linked worktree, which left the primary worktree reporting 96 phantom staged entries. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
e2d9b96 to
bdc283c
Compare
Summary
This repo is worked through many concurrent
git worktreecheckouts, several driven by agent sessions. A branch ref is shared by all of them — only the index and working tree are per worktree. Sogit checkout -B <name>does not merely move "your" branch: if another worktree has that name checked out, it is taken out from under it.Rule 6 states the general form: only ever create a new branch, or reset one you exclusively own. This is not specific to
main— every branch another worktree holds is equally off-limits.-Bis the specific trapGit already refuses the obvious forms when another worktree holds the branch:
git checkout -B main <ref>is not refused (observed on git 2.39.1). That single call creates the dual-checkout state the other two guards exist to prevent — and from then on every further force-move is legal, because moving a branch your own worktree has checked out is an ordinary reset. The guard never fires again.Why the symptom does not look like a ref problem
The victim worktree's
HEADis a symref, so it silently follows the branch to the new commit while its index and files stay at the old one. Since "staged" is by definition the HEAD↔index diff,git statusthere reports every intervening commit as staged modifications, in reverse — a file you added appears as a staged deletion.It is not cosmetic. A
git commitin that worktree would land a commit reverting everything between the two points, and would look entirely routine to whoever ran it.Provenance
Written from an incident in this repo: eight force-moves of
mainfrom a linked worktree, which left the primary worktree reporting 96 phantom staged entries — includingD .github/workflows/docs.yml, a file that had just been added by a merged PR. Nothing had been edited.The prescriptions are what the incident showed were actually needed:
Verification
-Bbypass reproduced and then reverted;refs/heads/mainvalue confirmed unchanged and the primary worktree confirmed clean afterwardsmarkdownlint-cli2clean (v0.20.0, the version pre-commit pins)