worktree-boundaries.md 6.7 KB

Worktree Boundaries

Never touch .claude/worktrees/ in any repo. Never touch git worktrees, submodules, gitlinks, or agent-spawned ephemeral dirs in repos outside your current working scope.

The rule

Worktrees belong to the project that owns them. If private-project housekeeping touches file X in repo Y, that does NOT extend to .claude/worktrees/ in repo Y. Worktrees are the private state of whichever agent, session, or human spawned them. They may look orphaned and aren't.

What counts as "don't touch"

  • Do not rm -rf .claude/worktrees/ in any repo
  • Do not git rm or git rm --cached worktree entries
  • Do not stage deletions of worktree dirs via git add -A (this is the subtle one — -A sweeps up changes you didn't intend)
  • Do not git clean -ff / --force --force in a repo with .claude/worktrees/ — a single -f git clean safely skips nested worktrees ("Skipping repository …"), but a double force deletes them, taking any uncommitted lane work with it
  • Do not commit changes that reference .claude/worktrees/ paths
  • Do not reason about whether a worktree is "orphaned" unless the owning project explicitly asks

Why

  • Worktree names like agent-<hash> look like ephemeral agent artifacts but may be active sessions
  • A gitlink/submodule pointing at a worktree is not garbage — it's a reference with meaning to the owning session
  • Agent-spawned worktrees may contain uncommitted work the user wants to inspect
  • Cross-project cleanup assumes context you don't have; when a project wants cleanup it will ask its own session

The specific failure this came from (2026-04-19)

During a private-project ecosystem-wide "commit + push all tool repos" pass, git add -A in flarecrawl staged gitlinks to 9 agent worktrees (from background agents running inside flarecrawl). Those gitlinks were committed and pushed as part of "chore: sync tool state". Then a subsequent rm -rf .claude/worktrees/ deleted the filesystem dirs, creating a dirty state. The user correctly pushed back — private-project housekeeping has no business touching another project's agent state.

Applied corrections when running bulk commits across repos

  • Use explicit file paths to git add, not -A or ., when the repo contains any .claude/ directory
  • Inspect git status --short before any bulk commit loop; if .claude/worktrees/ appears, STOP and ask
  • Never include worktrees in commit messages, scripts, or cleanup routines
  • If a repo's .claude/ state looks "dirty" during cross-project work, that's the repo's problem, not yours

Provisioning discipline — one writer per tree (parallel isolation)

The rule above is defensive (don't touch others' trees). This is the provisioning half: how to isolate your own parallel work so commits never clash. A git working tree has exactly one index and one HEAD — two sessions committing to the same tree is the write-time clash (index locks, interleaved commits, a working tree that mutates under you).

One writer, one worktree, one branch. Always. No exception for "small" work. "Feature branches vs worktrees" is a false choice at parallel scale — a branch is the ref that lands, a worktree is where it's written; you use both. Branches are unlimited and free; the scarce resource is the checkout — one directory has exactly one branch checked out at a time, so two sessions in the same folder are forced onto the same branch. Plain feature branches (one checkout, git switch) only work for sequential work.

Spawn type Isolation
Background agents (claude --bg) ✅ auto-worktree under .claude/worktrees/ — safe by default
Agent-tool subagents / /workflows agents that write set isolation: 'worktree' (read-only agents don't need it)
spawn_task chips do NOT isolate — the spawned session runs on the current branch of the primary checkout (claude-code#64605); seed the chip prompt to git switch -c <slug> first
Manual parallel sessions give each its own worktree; never two writing sessions in one checkout
Agent teams share one tree — only safe with file-partitioned, non-overlapping scopes

Detect a live peer writer before you write. The "worktree contract" is not enforced (chips violate it; an agent can escape a worktree via an absolute path), so don't assume isolation — verify it. When you start in a checkout whose tree is already dirty with changes you didn't make, probe before writing: fingerprint git diff | sha1sum twice ~6s apart and check the newest modified-file mtime. If the fingerprint changes (or a file was written seconds ago and you didn't do it), a peer session is live — warn and move your work to its own worktree rather than sharing the checkout. Old WIP with stale mtimes is fine. The session-start-unicode-scan.sh SessionStart hook raises this flag automatically at boot.

Corollaries:

  • The main/base checkout is sacred — landing-only. Never run a writing session there; it's the integration tree. Land parallel branches through fleet-ops (the manual, test-gated landing queue).
  • Land early, land often. Bound divergence by integrating green lanes continuously, not as one big-bang merge at the end — at 10+ lanes that's what bites. It's also your real safety net: committed lane work lives in the shared object store and survives even deletion of the worktree directory (recover with git worktree add <path> lane/<slug>); only uncommitted work is destructible. So commit-often is what makes a lane's work durable.
  • Lane location: default in-repo at <main>/.claude/worktrees/<slug> — tidy (no sibling dirs scattered across the parent), native to Claude Code's own worktrees, and gitignored so git add -A can't stage its gitlinks. This is only safe because .claude/worktrees/ is gitignored, so a tool placing a lane there must ensure that ignore first (new-lane.sh does). Use a sibling (<repo>-<slug>, outside the repo — new-lane.sh --sibling) only when you need structural isolation from repo-scoped destructive ops (git clean -ff, rm -rf <repo>) or in a repo that can't gitignore the dir.
  • Lane naming: lane/<slug> (and the native claude/<slug>) so the backlog is legible.
  • A branch name does not reveal isolationclaude/eager-wozniak looks identical whether it's a worktree-isolated background agent or a session in your main checkout. Isolation is a structural property you enforce, not something you read off the label.

Scope this rule covers

All projects. Never make exceptions "just for this session". If a worktree ever looks like it needs cleanup, ask the user explicitly before touching it.