/dotclaude:worktree — parallel-session isolation kit
You are setting up worktree-per-feature discipline: every substantive edit to
the policed repo happens in a dedicated git worktree, enforced by a blocking
PreToolUse hook. Read ../../principles/worktree-discipline.md FIRST — it
carries the doctrine and the five hard-won lessons this flow encodes; this
file is the procedure.
The hook is CONSUMED, not rendered. The plugin ships one debugged
check-main-checkout-edit.sh (in hooks/scripts/, wired always-on) that reads
a worktree: block from the project's dotclaude.yml and NO-OPs when there is
none. So this flow does NOT copy a hook into the project — it writes the config
the consumed hook reads. No per-project hook copy, no drift, and a project
tunes its policing in one data file.
The deliverable is not files — it is a proven install: the worktree:
config the consumed hook reads correctly, a smoke worktree that built the
project for real, and an explicit restart + live-fire handoff.
Phase 1 — Read the project's concurrency shape
Before any question:
Repo topology — is the project root the repo, or does the policed repo
sit below it?
git rev-parse --show-toplevel 2>/dev/null
for d in */; do git -C "$d" rev-parse --show-toplevel 2>/dev/null; done
git worktree list 2>/dev/null
Multiple sibling repos → ask which one(s) carry the collision risk (usually
the one being actively developed, not read-only references/snapshots).
Parallel-session evidence — lock files (.session_lock, *.lock at
repo root), CLAUDE.md sections about session coordination, git history
mentions of collisions/clobbered work, scheduled/background agents. This
calibrates urgency and whether a lease protocol already exists to narrow.
Git-ignored per-machine files (lesson 2 — the fresh-worktree breakers):
git -C <repo> status --ignored --porcelain | grep '^!!'
Filter to files/dirs the build actually needs: .env*, config/* pointers,
node_modules, credential stores. For each, note the likely recipe: copy a
committed *.example template / symlink from main / regenerate / skip.
Markdown-as-code signal (lesson 1): does the repo carry markdown the
system executes — policies/, workflows/, prompt libraries, agent
instructions, .claude/ kits? If yes, those paths must NOT be exempt.
Verify command — how the project proves itself (from package.json
scripts, Makefile, CI config, README): the build/test/render command the
smoke run (Phase 5) will execute, and the close-protocol gates.
Phase 2 — Interview
Open interview.md (same directory). 4-5 questions, adaptive — skip what
Phase 1 answered. Non-skippable even when scan feels conclusive: W2 (exempt
list / markdown-as-code) — it is a judgment call only the user can make.
Phase 3 — Read the principle
../../principles/worktree-discipline.md — in particular the
universal/project-specific split (what the worktree: config carries vs what
you author) and the five lessons (each maps to a concrete step below).
Phase 4 — Author the config + the lifecycle skill
Config (write the worktree: block to dotclaude.yml)
The consumed hook reads these keys; you do NOT render or wire a hook. Write (or
merge) a worktree: block into the project root's dotclaude.yml:
worktree:
policedRepo: <repo path relative to the project root; "." when root IS the repo>
namePrefix: <repo>-wt- # unless the user prefers otherwise
exempt: # main-checkout paths that stay freely editable
- "docs/*"
- "README.md"
- "LICENSE*"
setup: # fresh-worktree recipe, shown in the block message
- "cp config/instance.example.yaml <wt>/config/instance.yaml"
skillPath: .claude/skills/worktree/SKILL.md
exempt — the interviewed list, matched against the repo-relative path
(* spans directory separators). NEVER include *.md unless the user
explicitly confirmed no markdown is executed (markdown-as-code, lesson 1).
setup — the per-machine-files recipe (lesson 2), one line each; <wt>
stands for the worktree dir in the block message.
- The stray-worktree guard (lesson 5) and the escape hatch are built into the
consumed hook — no per-project authoring.
- Merge into an existing
dotclaude.yml (from another elicitation) — never
clobber the artifacts: / fileSize: keys.
Skill (in .claude-staging/skills/worktree/SKILL.md)
The project's lifecycle skill — author it (this one is project-specific by
nature). Sections, each filled with THIS project's real commands:
- Create + configure:
git -C <repo> worktree add ../<prefix><slug> -b feat/<slug> + the setup recipe (one line per per-machine file, real paths).
Then (lesson 5) author a capture + assert step: pull the worktree's
ABSOLUTE path from git -C <repo> worktree list --porcelain into a variable,
assert it (test -d "$WT/<build-marker>"), and tell the agent to use that
literal path for every later write — never recompose ../<prefix><slug> by
eye. Include the path-discipline note: ../ resolves against the
-C <repo> dir (not the shell cwd), write tools need an absolute path, and
the harness resets cwd between tool calls.
- Verify: the project's verify command + expected output.
- Close: the project's gates → commit (with any project-specific
pre-commit review the repo's conventions demand) → merge etiquette →
git worktree remove → branch cleanup. Include the derived-artifacts
"drop the noise" step with the EXACT paths Phase 5's smoke run recorded.
- Escape hatch: the
allow-main-edits convention, ask-first.
- Shared resources lease (only if Phase 1/interview surfaced out-of-repo
shared state): the narrowed lock protocol from the principle doc.
No wiring step
The consumed hook is wired once in the plugin's hooks/hooks.json (always-on,
NO-OP without a worktree: block). Do NOT add a local hook to the project's
.claude/settings.json — a project-level hook copy is exactly the drift this
model removes. The worktree: block written in Phase 4 is the entire opt-in.
CLAUDE.md section
A short "Session coordination" section for the project's CLAUDE.md: the rule,
the hook path, the exempt list, the escape hatch, the lifecycle skill pointer,
and — if a lease protocol survives — its narrowed scope (out-of-repo shared
resources + the merge window ONLY).
Phase 5 — Prove it live (mandatory, before presenting)
- Live-fire the consumed hook against the written config: feed it a few
synthetic tool inputs and confirm the verdicts.
H="$CLAUDE_PLUGIN_ROOT/hooks/scripts/check-main-checkout-edit.sh"
fire() { echo "{\"tool_input\":{\"file_path\":\"$1\"}}" | bash "$H"; echo "exit=$?"; }
fire "<policed-repo>/<a source file>" # expect exit 2 (block)
fire "<a markdown-as-code file>" # expect exit 2 if the project has any
fire "<an exempt path>" # expect exit 0
fire "<a real worktree>/<a file>" # expect exit 0
A wrong verdict means the worktree: block is off (wrong policedRepo, a bad
exempt glob) — fix the config, not a hook. The hook's own logic is tested in
the plugin; this proves THIS project's config drives it correctly.
- Smoke worktree (lesson 3): create a real worktree per the lifecycle
skill, run the setup recipe, run the project's verify command inside it.
- Record EVERY file
git status shows dirty afterwards — these are the
project's derived artifacts; write their exact paths into the lifecycle
skill's "drop the noise" close step.
- Re-run the Phase 5.1 live-fire with the smoke worktree's path (the
worktree-allowed case → expect exit 0).
- Tear down per the close protocol (
worktree remove must succeed WITHOUT
--force).
- If the verify command fails in the worktree but works in main — a
per-machine file is missing from the setup recipe. Fix the recipe, not the
worktree; re-run.
Phase 6 — Present + handoff
Present per dotclaude staging convention (inventory, highlight reasoning, what
was skipped and why). After approval, move .claude-staging/ → .claude/,
merge the worktree: block into dotclaude.yml, commit. (No settings-fragment
merge — the hook is plugin-wired and reads the config.)
Then the two lines every install ends with (lesson 4):
"Hooks register at session start — the block is NOT active in this session
or in already-open ones. Restart, then live-fire: ask for a trivial edit to
<a policed file> and confirm the hook blocks with the worktree
instructions. Until each parallel session restarts, it remains unpoliced."
Non-negotiable rules for this flow
- Never blanket-exempt
*.md by default. The markdown-as-code question
(W2) is mandatory. Projects whose markdown is the program (policies,
prompts, agent kits) need those paths POLICED; a docs-are-free default
silently unguards their behavioral layer.
- The hook is consumed, never copied. The plugin ships one debugged
check-main-checkout-edit.sh (nearest-existing-dir walk, worktree detection,
other-repo pass-through, stray-worktree guard) that reads the worktree:
block. Write config, not a hook — never author a local hook copy or a
.claude/settings.json hook entry; that project-level copy is exactly the
drift this model kills. If the hook needs logic it lacks, that's a dotclaude
PR, not a local fork.
- Prove the config drives the hook. The hook's own logic is tested in the
plugin; this install's job is to live-fire the consumed hook against THIS
project's
worktree: block (Phase 5.1). A green block / allow / exempt /
worktree set is the proof the config is right.
- The smoke run is not optional. Derived-artifact dirt and missing
per-machine files are only discoverable by actually building in a fresh
worktree. An install presented without a smoke run has unknown failure
modes scheduled for the user's first real feature.
- Opt-in only. Never wire this into plugin-level always-on hooks; never
install it uninvited on a project with no concurrency signal — for a solo
single-session project, recommend AGAINST installing (say so explicitly in
the applicability decision).
- Worktrees + lease, not worktrees instead of lease. If out-of-repo
shared resources exist (data snapshots, device pools, the merge window),
ship the narrowed lease protocol alongside; if a broad lock protocol
already exists, narrow it rather than deleting it.
1---2name: worktree3description: Set up worktree-per-feature discipline for a project where several AI sessions (or humans + agents) work concurrently and collide in one checkout. Writes a thin `worktree:` config block that the plugin's CONSUMED main-checkout hook reads (no rendered per-project hook copy), plus a project-specific worktree lifecycle skill — calibrated by interview (which repo, what's exempt, how a fresh worktree gets configured) and PROVEN by a live smoke worktree before handoff. Invoke /dotclaude:worktree when parallel sessions are real or planned.4---56# `/dotclaude:worktree` — parallel-session isolation kit78You are setting up worktree-per-feature discipline: every substantive edit to9the policed repo happens in a dedicated git worktree, enforced by a blocking10PreToolUse hook. Read `../../principles/worktree-discipline.md` FIRST — it11carries the doctrine and the five hard-won lessons this flow encodes; this12file is the procedure.1314**The hook is CONSUMED, not rendered.** The plugin ships one debugged15`check-main-checkout-edit.sh` (in `hooks/scripts/`, wired always-on) that reads16a `worktree:` block from the project's `dotclaude.yml` and NO-OPs when there is17none. So this flow does NOT copy a hook into the project — it writes the config18the consumed hook reads. No per-project hook copy, no drift, and a project19tunes its policing in one data file.2021The deliverable is not files — it is a **proven** install: the `worktree:`22config the consumed hook reads correctly, a smoke worktree that built the23project for real, and an explicit restart + live-fire handoff.2425## Phase 1 — Read the project's concurrency shape2627Before any question:28291. **Repo topology** — is the project root the repo, or does the policed repo30 sit below it?31 ```bash32 git rev-parse --show-toplevel 2>/dev/null33 for d in */; do git -C "$d" rev-parse --show-toplevel 2>/dev/null; done34 git worktree list 2>/dev/null35 ```36 Multiple sibling repos → ask which one(s) carry the collision risk (usually37 the one being actively developed, not read-only references/snapshots).38392. **Parallel-session evidence** — lock files (`.session_lock`, `*.lock` at40 repo root), CLAUDE.md sections about session coordination, git history41 mentions of collisions/clobbered work, scheduled/background agents. This42 calibrates urgency and whether a lease protocol already exists to narrow.43443. **Git-ignored per-machine files** (lesson 2 — the fresh-worktree breakers):45 ```bash46 git -C <repo> status --ignored --porcelain | grep '^!!'47 ```48 Filter to files/dirs the build actually needs: `.env*`, `config/*` pointers,49 `node_modules`, credential stores. For each, note the likely recipe: copy a50 committed `*.example` template / symlink from main / regenerate / skip.51524. **Markdown-as-code signal** (lesson 1): does the repo carry markdown the53 *system executes* — `policies/`, `workflows/`, prompt libraries, agent54 instructions, `.claude/` kits? If yes, those paths must NOT be exempt.55565. **Verify command** — how the project proves itself (from `package.json`57 scripts, Makefile, CI config, README): the build/test/render command the58 smoke run (Phase 5) will execute, and the close-protocol gates.5960## Phase 2 — Interview6162Open `interview.md` (same directory). 4-5 questions, adaptive — skip what63Phase 1 answered. Non-skippable even when scan feels conclusive: **W2 (exempt64list / markdown-as-code)** — it is a judgment call only the user can make.6566## Phase 3 — Read the principle6768`../../principles/worktree-discipline.md` — in particular the69universal/project-specific split (what the `worktree:` config carries vs what70you author) and the five lessons (each maps to a concrete step below).7172## Phase 4 — Author the config + the lifecycle skill7374### Config (write the `worktree:` block to `dotclaude.yml`)7576The consumed hook reads these keys; you do NOT render or wire a hook. Write (or77merge) a `worktree:` block into the project root's `dotclaude.yml`:7879```yaml80worktree:81 policedRepo: <repo path relative to the project root; "." when root IS the repo>82 namePrefix: <repo>-wt- # unless the user prefers otherwise83 exempt: # main-checkout paths that stay freely editable84 - "docs/*"85 - "README.md"86 - "LICENSE*"87 setup: # fresh-worktree recipe, shown in the block message88 - "cp config/instance.example.yaml <wt>/config/instance.yaml"89 skillPath: .claude/skills/worktree/SKILL.md90```9192- **`exempt`** — the interviewed list, matched against the repo-relative path93 (`*` spans directory separators). NEVER include `*.md` unless the user94 explicitly confirmed no markdown is executed (markdown-as-code, lesson 1).95- **`setup`** — the per-machine-files recipe (lesson 2), one line each; `<wt>`96 stands for the worktree dir in the block message.97- The stray-worktree guard (lesson 5) and the escape hatch are built into the98 consumed hook — no per-project authoring.99- Merge into an existing `dotclaude.yml` (from another elicitation) — never100 clobber the `artifacts:` / `fileSize:` keys.101102### Skill (in `.claude-staging/skills/worktree/SKILL.md`)103104The project's lifecycle skill — author it (this one is project-specific by105nature). Sections, each filled with THIS project's real commands:1061071. **Create + configure**: `git -C <repo> worktree add ../<prefix><slug> -b108 feat/<slug>` + the setup recipe (one line per per-machine file, real paths).109 Then (lesson 5) author a **capture + assert** step: pull the worktree's110 ABSOLUTE path from `git -C <repo> worktree list --porcelain` into a variable,111 assert it (`test -d "$WT/<build-marker>"`), and tell the agent to use that112 literal path for every later write — never recompose `../<prefix><slug>` by113 eye. Include the path-discipline note: `../` resolves against the114 `-C <repo>` dir (not the shell cwd), write tools need an absolute path, and115 the harness resets cwd between tool calls.1162. **Verify**: the project's verify command + expected output.1173. **Close**: the project's gates → commit (with any project-specific118 pre-commit review the repo's conventions demand) → merge etiquette → 119 `git worktree remove` → branch cleanup. Include the derived-artifacts120 "drop the noise" step with the EXACT paths Phase 5's smoke run recorded.1214. **Escape hatch**: the `allow-main-edits` convention, ask-first.1225. **Shared resources lease** (only if Phase 1/interview surfaced out-of-repo123 shared state): the narrowed lock protocol from the principle doc.124125### No wiring step126127The consumed hook is wired once in the plugin's `hooks/hooks.json` (always-on,128NO-OP without a `worktree:` block). Do NOT add a local hook to the project's129`.claude/settings.json` — a project-level hook copy is exactly the drift this130model removes. The `worktree:` block written in Phase 4 is the entire opt-in.131132### CLAUDE.md section133134A short "Session coordination" section for the project's CLAUDE.md: the rule,135the hook path, the exempt list, the escape hatch, the lifecycle skill pointer,136and — if a lease protocol survives — its narrowed scope (out-of-repo shared137resources + the merge window ONLY).138139## Phase 5 — Prove it live (mandatory, before presenting)1401411. **Live-fire the consumed hook against the written config**: feed it a few142 synthetic tool inputs and confirm the verdicts.143 ```bash144 H="$CLAUDE_PLUGIN_ROOT/hooks/scripts/check-main-checkout-edit.sh"145 fire() { echo "{\"tool_input\":{\"file_path\":\"$1\"}}" | bash "$H"; echo "exit=$?"; }146 fire "<policed-repo>/<a source file>" # expect exit 2 (block)147 fire "<a markdown-as-code file>" # expect exit 2 if the project has any148 fire "<an exempt path>" # expect exit 0149 fire "<a real worktree>/<a file>" # expect exit 0150 ```151 A wrong verdict means the `worktree:` block is off (wrong `policedRepo`, a bad152 exempt glob) — fix the config, not a hook. The hook's own logic is tested in153 the plugin; this proves THIS project's config drives it correctly.1542. **Smoke worktree** (lesson 3): create a real worktree per the lifecycle155 skill, run the setup recipe, run the project's verify command inside it.156 - Record EVERY file `git status` shows dirty afterwards — these are the157 project's derived artifacts; write their exact paths into the lifecycle158 skill's "drop the noise" close step.159 - Re-run the Phase 5.1 live-fire with the smoke worktree's path (the160 worktree-allowed case → expect exit 0).161 - Tear down per the close protocol (`worktree remove` must succeed WITHOUT162 `--force`).1633. If the verify command fails in the worktree but works in main — a164 per-machine file is missing from the setup recipe. Fix the recipe, not the165 worktree; re-run.166167## Phase 6 — Present + handoff168169Present per dotclaude staging convention (inventory, highlight reasoning, what170was skipped and why). After approval, move `.claude-staging/` → `.claude/`,171merge the `worktree:` block into `dotclaude.yml`, commit. (No settings-fragment172merge — the hook is plugin-wired and reads the config.)173174Then the two lines every install ends with (lesson 4):175176> "Hooks register at session start — the block is NOT active in this session177> or in already-open ones. Restart, then live-fire: ask for a trivial edit to178> `<a policed file>` and confirm the hook blocks with the worktree179> instructions. Until each parallel session restarts, it remains unpoliced."180181## Non-negotiable rules for this flow1821831. **Never blanket-exempt `*.md` by default.** The markdown-as-code question184 (W2) is mandatory. Projects whose markdown is the program (policies,185 prompts, agent kits) need those paths POLICED; a docs-are-free default186 silently unguards their behavioral layer.1872. **The hook is consumed, never copied.** The plugin ships one debugged188 `check-main-checkout-edit.sh` (nearest-existing-dir walk, worktree detection,189 other-repo pass-through, stray-worktree guard) that reads the `worktree:`190 block. Write config, not a hook — never author a local hook copy or a191 `.claude/settings.json` hook entry; that project-level copy is exactly the192 drift this model kills. If the hook needs logic it lacks, that's a dotclaude193 PR, not a local fork.1943. **Prove the config drives the hook.** The hook's own logic is tested in the195 plugin; this install's job is to live-fire the consumed hook against THIS196 project's `worktree:` block (Phase 5.1). A green block / allow / exempt /197 worktree set is the proof the config is right.1984. **The smoke run is not optional.** Derived-artifact dirt and missing199 per-machine files are only discoverable by actually building in a fresh200 worktree. An install presented without a smoke run has unknown failure201 modes scheduled for the user's first real feature.2025. **Opt-in only.** Never wire this into plugin-level always-on hooks; never203 install it uninvited on a project with no concurrency signal — for a solo204 single-session project, recommend AGAINST installing (say so explicitly in205 the applicability decision).2066. **Worktrees + lease, not worktrees instead of lease.** If out-of-repo207 shared resources exist (data snapshots, device pools, the merge window),208 ship the narrowed lease protocol alongside; if a broad lock protocol209 already exists, narrow it rather than deleting it.