Playbook: Shared Checkout Worktree Isolation Enforcement
Created: 2026-07-08 Incident: Two stray commits from unrelated sessions landed on this orchestrating session’s checked-out branch within a single working session Issue: #3230
Trigger
Multiple concurrent Claude Code sessions/agents (main session, background Agent() calls, other
human/agent sessions on the same machine) all operate against the same primary checkout
(C:\code_2026\p24-infra) instead of isolated tmp/wt-{issue} worktrees mandated by
CLAUDE.md §Agent Workflow — Worktree Branch Isolation.
Whichever session runs git checkout <branch> last determines what’s checked out. If a second
session then runs git commit without first switching to its own branch, the commit lands on
whatever branch the first session left checked out — silently, with no error.
Symptoms
git log --oneline -3shows an unexpected commit authored by a different task/topic than what you’re working ongit branch --show-currentshows a branch name unrelated to your current task (e.g.,security/3216-jwt-rotationorrc/3223-atrax-n8n-bms4when you expectedmain)- A commit you just made doesn’t appear where expected, or an extra commit appears above yours in
git log
Root Cause
This is not a bug in any single session’s logic — every session correctly runs git commit in
“the current directory.” The problem is that the current directory is shared, mutable state
across concurrent sessions, and nothing enforced the documented worktree-isolation policy
technically — it was documentation-only, easy to forget under task pressure.
Observed twice in one session on 2026-07-08:
- A commit intended for
docs/is-3218-...landed onsecurity/3216-jwt-rotation(a JWT rotation branch from an unrelated concurrent session) - Minutes later, the working directory had switched again to
rc/3223-atrax-n8n-bms4(an ATRAX n8n branch from yet another concurrent session)
Immediate Recovery (if you discover a stray commit)
Do NOT discard it — it may be someone else’s real, unmerged work.
# 1. Identify the stray commit(s) on top of / mixed into your branch
git log --oneline -5
# 2. Rescue it onto its own branch before touching anything
git branch rescue/<topic>-<short-desc> <stray-commit-sha>
git push origin rescue/<topic>-<short-desc>
# 3. Reset your own branch back to remove the stray commit
# (only if it hasn't been pushed anywhere with the stray commit included)
git reset --hard origin/<your-branch> # or origin/main if branch is new
# 4. Redo your own work cleanly in an isolated worktree (see Prevention below)
# 5. File an issue documenting the rescued branch so a human/session can
# properly re-file it under the correct issue number and review itPermanent Fix — Technical Enforcement (applied 2026-07-08)
Documentation alone (CLAUDE.md §Worktree Branch Isolation) was not sufficient — it relies on every
session remembering to opt in. Added a technical guard to both safety hooks:
.claude/hooks/pre-bash-safety.sh(Bash tool).claude/hooks/pre-bash-safety-windows.ps1(PowerShell tool)
Detection logic: a linked git worktree’s --git-dir points to .git/worktrees/<name>, while the
primary checkout’s --git-dir equals its --git-common-dir. Any git commit, git checkout -b,
git switch -c, or git branch -m/-M run where these are equal (i.e., in the primary checkout)
is now blocked with a message directing to git worktree add.
# This now fails in the primary checkout:
cd C:\code_2026\p24-infra
git commit -m "..."
# BLOCKED: git commit / branch-create in the PRIMARY checkout, not an isolated worktree (#3230)...
# This still works — inside any linked worktree:
cd C:\code_2026\p24-infra\tmp\wt-1234
git commit -m "..."
# proceeds normallyWhat is NOT blocked (intentionally — these are read-only/navigational, needed even in the
primary checkout): git checkout <existing-branch> (no -b), git pull, git fetch, git status,
git log, git worktree add itself.
Standard Workflow (unchanged, now technically enforced)
# 1. Create an isolated worktree for this issue
git worktree add tmp/wt-{issue} -b {branch-name} origin/main
# 2. Do ALL commit/branch work inside the worktree
cd tmp/wt-{issue}
# ... edits, git add, git commit — all fine here ...
git push -u origin {branch-name}
# 3. Open PR, review, merge as usual
# 4. Clean up — remove, then GUARANTEE the dir is gone (git may leave it behind), then prune
cd ../..
git worktree remove --force tmp/wt-{issue}
rm -rf tmp/wt-{issue} # leftover unregistered dir silently falls through to the parent repo — #6006
git worktree prune -vA worktree dir left behind after
git worktree remove(Windows file-lock, stale checkout) makes git silently operate on the parent repo’s current branch. Seeworktree-remove-fallthrough-guard.mdfor the root cause and the mandatory pre-writegit rev-parse --show-toplevelguard.
Limitations
- This guard only protects sessions running through Claude Code’s hook system on this machine.
It does not protect against: manual human
gitcommands run outside Claude Code, or other machines/CI runners operating on a clone of this repo. - It cannot undo damage from before 2026-07-08 — only prevents new occurrences going forward.
- Background
Agent()calls withisolation: "worktree"already get their own isolated worktree automatically (unrelated mechanism) — this hook guards the orchestrating session and anyAgent()calls made without that isolation flag that end up sharing the primary checkout.
Related
CLAUDE.md§Agent Workflow — Worktree Branch Isolation (the pre-existing documented policy this hook now enforces technically)concurrent-sops-write-worktree-race.md— the sibling filesystem-level race (#5242): a SOPS write is not agit commit, so the hook guard here never fires on it; that playbook adds a pre-flight guard for shared-file/SOPS writes- Issue #3230
- Rescued branch example:
rescue/3161-test-net-v42(re-filed as issue #3233, PR #3234)