| name | dev-repo-worktrees |
| description | Per-session git worktree pattern for ~/dev/<repo> code repos. Prevents the CONCURRENT-SESSION-HEAD-DRIFT bug class where two Claude Code sessions writing to the same physical checkout collide on the single .git/HEAD pointer + index + working tree. Ships a wrapper script + hookify warn rule + the rule prose. Complements dev-repo-checkpoints (recovery) by preventing the failure at the structural layer (isolation). |
| trigger | install the wrapper at ~/.local/bin/claude-dev-worktree + the hookify rule at the vault's .claude/. Call `claude-dev-worktree start <repo> <slug>` at session start when the session is going to write code to ~/dev/<repo>/. |
| source | codified after the second concurrent-Claude-session HEAD-drift collision in 16 hours |
Dev-Repo Worktrees
Per-session git worktree pattern for ~/dev/<repo>/ code repos that prevents concurrent Claude Code sessions from colliding on the shared .git/HEAD.
Why this exists
The failure mode (CONCURRENT-SESSION-HEAD-DRIFT): Two Claude Code sessions both editing code in ~/dev/<repo>/ share one physical checkout, which means one .git/HEAD + one index + one working tree. When session A checks out their feature branch, session B's next git commit lands on session A's branch by accident. Recovery requires branch-label manipulation + git stash dances + branch-switch-safety hook overrides. Observed multiple times in long-running multi-session workflows.
The cause: Bash cd is per-command, not per-session. Every invocation re-cds; HEAD-state from prior commands is whatever the last command left it. Bash sessions cannot share filesystem state with other Bash sessions — but git operations on the same checkout absolutely DO share state, and the model can't observe that.
The fix: every Claude Code session that writes code in ~/dev/<repo>/ works in its own per-session git worktree at ~/dev/<repo>-<slug>/. Each worktree has its own HEAD + own index + own working tree, while sharing the underlying object store. Zero collision possible.
~/dev/<repo>/ NEVER write here in a session
~/dev/<repo>-<slug>/ YES — session checkout, isolated HEAD
The main checkout stays as the maintenance + reference path — fetches + integration view. Session writes go through the worktree.
When the pattern DOES apply
- Multi-step feature work in
~/dev/<repo>/
- Anything that involves
git commit or git push from the session
- Any task that runs builds / tests / code modifications
When the pattern does NOT apply
- Single-file edits (typo fix, README tweak) — overhead exceeds value
- Read-only audits (
cat, grep, git log, git diff) — no HEAD writes
- Vault git (markdown notes / second-brain) — vault has its own worktree mechanism with different semantics (see
superpowers:using-git-worktrees)
Install
1. Drop the wrapper script
Copy claude-dev-worktree to ~/.local/bin/claude-dev-worktree:
cp skills/dev-repo-worktrees/claude-dev-worktree ~/.local/bin/claude-dev-worktree
chmod +x ~/.local/bin/claude-dev-worktree
Verify on PATH: which claude-dev-worktree.
2. (Optional) Drop the hookify warn rule
Copy hookify.warn-dev-repo-shared-checkout.local.md to your vault's .claude/ directory and customize the (your-repo-1|your-repo-2|...) capture group in the pattern to match your actual high-concurrency repos.
The hookify rule fires on cd ~/dev/<known-shared-repo> (no worktree suffix) and surfaces the pattern. Action is warn, not block — false-positive rate is acceptable because every miss-as-warn-only is just a reminder, never a block of legitimate work.
3. Reference the rule from your CLAUDE.md
Add to your CLAUDE.md # Rules section:
- **External-repo worktrees** (`<your-rules-dir>/external-repo-worktrees.md`):
every Claude session that writes code in `~/dev/<repo>/` MUST work in a
per-session git worktree at `~/dev/<repo>-<slug>/`, NEVER in the main
checkout. Wrapper script `~/.local/bin/claude-dev-worktree start <repo>
<slug>` automates creation; `cleanup <repo> <slug>` removes after PR
merges. Bypass: `DEV_WORKTREE_BYPASS=1` for read-only audit work.
The rule file itself is documented in this SKILL.md — the wrapper + hookify are the enforcement layers; the CLAUDE.md entry is the discoverability layer.
Usage
At session start, BEFORE first edit on a ~/dev/<repo> issue:
claude-dev-worktree start <repo> <slug>
cd ~/dev/<repo>-<slug>
At session close (after PR merges):
claude-dev-worktree cleanup <repo> <slug>
Inspect every active worktree across all repos under ~/dev/:
claude-dev-worktree list
How it compares to other patterns
| Pattern | Mechanism | When it fires |
|---|
This skill (dev-repo-worktrees) | Per-session git worktree add | Session start, BEFORE first write |
dev-repo-checkpoints skill | Auto-stash on Stop event | Session end, AFTER work would have been lost |
branch-switch-safety rule | PreToolUse block on destructive ops with untracked work | When a destructive op is attempted |
The three patterns are complementary:
dev-repo-worktrees PREVENTS the collision by structural isolation
dev-repo-checkpoints RECOVERS lost work via auto-stash
branch-switch-safety BLOCKS the destructive op when work would be lost
A robust setup runs all three. Worktree-per-session is the cheapest insurance — disk cost is ~500MB per worktree (typical Node + Cargo project), trivially less than the cost of one corruption incident.
Enforcement companions (hooks in the starter hooks/)
The worktree convention above is the structural fix; two PreToolUse / SessionStart hooks enforce it so discipline doesn't silently lapse under load. Both ship in this starter's hooks/ directory.
check-cd-outside-worktree.py — blocks the cd-into-main move
A worktree has its own HEAD; the main checkout has a different HEAD shared with every other main-checkout session. The classic recurrence is a worktree-rooted session that cds back into the main checkout and then commits — landing the commit on the wrong branch. This PreToolUse(Bash) hook detects a worktree-rooted session (cwd under <repo>/.claude/worktrees/<slug>) and blocks any cd/pushd whose resolved target lands in the main tree but outside the worktrees subtree. Use git -C <path> … for read-only queries against the main checkout instead. Bypass: WORKTREE_CD_BYPASS=1.
session-lock.py — sibling-session coordination
Worktree isolation prevents the shared-HEAD collision structurally, but two sessions can still pick the SAME repo and clobber each other's in-flight work (e.g. a sibling commits a broken state, CI fails, it reverts, and ~45 min of work is wiped). The coordination layer:
- SessionStart: resolves the MAIN repo root (
git rev-parse --git-common-dir, shared across every worktree of the repo), reads <main_root>/.claude/.session-lock.json, and warns if a DIFFERENT session was active in the repo within the last 5 min. Then writes / refreshes this session's lock.
- Stop: bumps
last_activity_at so an actively-working session keeps the slot warm and a later sibling still sees it as live.
- Auto-expires after 30 min idle; the per-session cache is pruned after 7 days. Bypass:
SIBLING_SESSION_LOCK_BYPASS=1 (intentional parallel / collaborative work).
Install both (canonical runtime location is ~/.claude/hooks/):
cp hooks/check-cd-outside-worktree.py hooks/check-py-import-precommit.py hooks/session-lock.py ~/.claude/hooks/
Then wire them in ~/.claude/settings.json — check-cd-outside-worktree.py + check-py-import-precommit.py under PreToolUse (matcher Bash), and session-lock.py under both SessionStart and Stop:
{ "type": "command", "command": "python3 ~/.claude/hooks/session-lock.py" }
check-py-import-precommit.py is the third companion: it runs ruff check --select F821 on staged .py before a git commit and warn-blocks on undefined-name findings (the missing-import class that triggers the broken-commit-then-revert collision). Requires ruff on PATH (uv tool install ruff); it nudges once/day if absent rather than failing silently. Bypass: PRECOMMIT_F821_BYPASS=1.
Disk cost
Each worktree is a full checkout. With 3 concurrent sessions on a typical fullstack repo (~500MB working tree with build-tool caches like target/, .next/, .gradle/, etc.), worktrees add ~1.5GB. Some package managers share their store across worktrees automatically (e.g. pnpm via node_modules/.pnpm); language build caches (cargo target/, gradle .gradle/) are typically per-worktree and cannot easily share without symlink tricks.
Not a legitimate blocker. Disk is cheap; mid-build corruption is not.
Bug class
CONCURRENT-SESSION-HEAD-DRIFT. Parent class: ARTIFACT-WITHOUT-ISOLATION-WIRING — siblings include ARTIFACT-WITHOUT-UMBRELLA-WIRING (skill installs without routing wires) and ARTIFACT-WITHOUT-DEPENDENCY-WIRING (Linear issues created without blocker relations). All share the same shape: a thing gets created without its required structural connection wired in the same beat.
Bypass
DEV_WORKTREE_BYPASS=1 for legitimate single-session work in the main checkout — rare, typically only when fetching + reading without writes.
Credit
Originally codified in a personal Claude vault as external-repo-worktrees.md after the second concurrent-session HEAD-drift collision in 16 hours. The pattern is universal — published to the public substrate so anyone running multi-session Claude Code workflows can install it without rediscovering the failure mode.