Worktree isolation: Edit-tool path validation + drift-detection in task-notification payload
1. Problem
When Claude Code spawns multiple agents in parallel via Task(isolation: "worktree"),
each agent gets its own git worktree on disk. Each worktree has its own HEAD (under.git/worktrees/<name>/HEAD), but the refs database is shared with the common repo:refs/heads/<branch> is a single entry visible from every worktree, so a git checkout
issued against any worktree's .git/worktrees/<name> moves only that worktree's HEAD —
including the meta-root's worktree when an agent's CWD has drifted there. Two failure
modes:
(1) Edit-tool path drift. An agent's Edit/Write call with an absolute file_path that
happens to fall inside the meta-root checkout (e.g. an unqualified path that the agent
inferred from session context) lands in the meta-root working tree, not the worktree.
The agent's diff appears empty in the worktree; the meta-root accumulates uncommitted
leakage from concurrent agents.
(2) Branch-checkout drift. An agent running git checkout <branch> from a CWD that
has drifted to the meta-root (or via an explicit git -C <meta-root>) moves the
meta-root's HEAD, displacing whatever other agents (or the operator) had checked out
there. With N concurrent agents, HEAD can move N times during a single fan-out.
Concrete evidence from a single recent fan-out run in our orchestration framework:
- Incident 1 — Edit calls landed at meta-root; the agent caught its own divergence and re-applied in the worktree.
- Incident 2 — meta-root branch switched to a different feature branch mid-fan-out, displacing the operator's uncommitted in-progress work.
- Incident 3 — 6 test files leaked into the meta-root working tree from a sibling agent; recovered via
git stash.
Prior workarounds we have shipped at the orchestration layer:
- Pre-dispatch prompt clause embedded in every parallel-worker spawn that instructs the
agent to keep edits and git operations inside $PWD. Soft constraint; LLM-orchestrator
subagents still violate on long procedures.
- Pre-dispatch file-touch overlap detection that refuses parallel dispatch when briefs
declare overlapping file-touch sets. Catches cross-worker file collisions but not
worker-vs-meta-root path drift.
- A post-fan-out capture/verify guard (shell script) that snapshots the meta-root HEAD
- branch + working-tree hash before spawn and checks for drift after every agent
completes. Post-hoc only; surfaces drift after agents complete, but cannot prevent
mid-flight work loss.
2. Requested platform changes
Request A: Edit-tool path validation against worktree root
When the Agent SDK's Edit tool is invoked from inside an agent that was spawned withisolation: "worktree", the tool MUST reject any file_path whose canonicalized
absolute form is not path-boundary-contained within the agent's worktree root — i.e.
after realpath-resolution, the file_path must be the worktree root itself or a
descendant of it on a path-component boundary. String-prefix matching is
insufficient: a naive startswith check would incorrectly accept a sibling worktree
at /tmp/wt-other/file.py when the root is /tmp/wt, contradicting the rejection of
sibling worktrees in the behavior matrix below. Equivalent safe forms in stdlib includePath(file_path).resolve().is_relative_to(Path(worktree_root).resolve()) or an
explicit parents-chain walk; the contract is path-component containment, not
character-prefix containment. The rejection should be a structured error
(e.g. WORKTREE_PATH_VIOLATION) carrying the offending path and the worktree root
so the agent's diagnostic surface can recover or surface the violation.
Behavior matrix:
| Agent isolation | file_path resolves inside worktree | Result |
|---|---|---|
| worktree | yes | proceed |
| worktree | no (meta-root, sibling worktree, anywhere else) | reject with WORKTREE_PATH_VIOLATION |
| not isolated | n/a | unchanged (today's behavior) |
Notes:
- The same gate SHOULD apply to Write and NotebookEdit (parallel tools that mutate paths).
- The gate SHOULD NOT apply to Read, since cross-tree reads are intentional (the
prompt clause already directs agents to use absolute Read paths instead of cd).
Bashis outside scope of this request — see Request B for the drift-detection signal
that closes the Bash surface programmatically.
Request B: Drift-detection field in Agent task-notification payload
When an Agent completes (the parent receives the task-notification message), the payload
SHOULD include a boolean meta_root_state_drift field. The field is true iff any of:
- the meta-root's HEAD ref differs from its state at agent-spawn time;
- the meta-root's working-tree dirty state changed during the agent's lifetime;
- the meta-root's branch HEAD changed during the agent's lifetime.
This lets orchestrators detect drift programmatically without each orchestrator having
to reimplement the same git rev-parse + working-tree hash dance in a shell script.
Once the field exists, our (and every other orchestrate-style framework's) local
capture/verify guard can be deprecated and replaced with a single payload read.
3. Why prompt-only fixes are insufficient
Our coding convention for LLM pipeline design captures the structural limit:
"DO NOT silently fall back to X" or "MUST invoke subprocess Y" in an agent spec is a soft constraint: an LLM-orchestrator subagent will still violate when the procedure has many steps.
The agents observed in the evidence above had the Scope Guard prompt clause in their
spawn prompts and still drifted. The platform-side fix in Request A makes the violations
structurally impossible, not socially discouraged.
4. Compatibility / migration
- Request A is backward-compatible for agents that do not use
isolation: "worktree". - Request A may break workflows that intentionally write outside the worktree; we believe
the right behavior is to fail loud and force the workflow author to make the
cross-tree write explicit (e.g. via a new escape-hatch flag), but the request is open
to Anthropic on the exact escape mechanism.
- Request B is purely additive (new optional field); orchestrators that don't read it
see no behavior change.
5. Out of scope (acknowledged)
- A
cd/git checkoutPreToolUse hook for Bash. We considered this as a local
workaround and rejected it: it cannot reach the Edit/Write surface that dominates the
evidence above, and the CWD-based agent-identity heuristic is bypassed precisely when
drift has already happened. Bash surface drift remains observable through Request B's
meta_root_state_drift signal.
- Per-tool sandboxing beyond path validation (e.g. capability tokens, syscall filters).
Out of scope for this request; the path-validation approach is the minimal change that
closes the documented evidence.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗