Agent tool with isolation:"worktree" silently reuses stale branches on agentId-prefix collision
Summary
When the Agent tool is called with isolation: "worktree", Claude Code derives the worktree's git branch name from an 8-hex-char prefix of the subagent's agentId (worktree-agent-<8hex>). If a branch with that name already exists from a prior session, the worktree is silently reused — including that branch's uncommitted files, stash stack, and whatever state was left behind. This led to contamination of a new subagent's work with a 2-day-old session's leftover changes, with no warning in the subagent's context that the worktree wasn't freshly created.
Reproduction (observed today)
- Prior session (2 days ago) used
Agent(isolation:"worktree")which created branchesworktree-agent-aae6004aandworktree-agent-abb767eb. Those sessions ended with uncommitted changes andgit stashentries on those branches (e.g.stash@{3}: On main: other-agent WIP (policy/keys/attestation/etc.)). - Today, a new session called
Agent(isolation:"worktree")twice. The new subagents were assigned agentIdsaae6004a1fe933672andabb767eb49c7841fc— whose 8-hex prefixes collide exactly with the 2-day-old branches. - Claude Code's worktree setup found the existing branches, reused them instead of creating fresh ones, and the subagents inherited:
- 10 stacked stashes (including work labeled
"other-agent WIP") - Uncommitted file modifications from prior sessions
- An outdated base (
e47a4073, 2 days old) instead of currentmain(d89ba7d2)
- One subagent followed a rebase-and-stash-pop recovery path per the user's instruction, and the
git stash popauto-merged a stale rename (CORE_PRIMITIVES→CORE_PRIMITIVES_FALLBACK) plus stray doc-comment edits into its work. The contamination passed tests + clippy because it was semantically compatible; only a careful manual diff audit caught the out-of-scope hunks. - The second subagent crashed after 380 tool_uses / 31 minutes with \"Prompt is too long\", never committing anything. Its worktree was left with cross-scope files (including test files that belonged to the OTHER subagent's scope) — suggesting even more state leakage in the reused environment.
Impact
- Silent data corruption of agent output. Stale work from an orphaned prior session was merged into a new commit claiming to address a specific needle. The contaminated commit needed a post-hoc manual audit to discover. A less-attentive user would have merged the polluted commit to main.
- Wasted agent time / cost. The second agent burned 31 minutes of sonnet wall-time fighting a contaminated worktree before context-overflow killed it, producing zero output.
- Cross-scope file leakage. Files created by one subagent appeared in the other subagent's worktree, indicating the worktrees are not as isolated as the API name suggests.
Expected behavior
- On
isolation: \"worktree\", the tool should refuse to reuse an existing branch+worktree. Either: - Option A: always create a fresh, uniquely-named worktree (append timestamp or longer agentId prefix) — and guarantee the starting tree is clean (no stashes, no untracked files, no stale uncommitted work).
- Option B: if a branch with the derived name already exists, either error loudly (\"branch name collision; pass explicit branch\") or hard-reset the worktree to the intended base (
HEADof the calling session) +git clean -fdx+git stash clearbefore handing control to the subagent. - The subagent should be told the worktree is freshly created (explicitly, in the system prompt or equivalent) so it can trust its base.
agentIdprefix collisions should be either impossible (longer prefix) or explicitly handled.
Environment
- macOS 15.4
- Claude Code version: current as of 2026-04-21
- Subagent type:
general-purposewithmodel: sonnet isolation: \"worktree\"via main session'sAgenttool
Suggested fix priority
High — this is silent data corruption in a feature whose name (\"worktree isolation\") implies the opposite property.
Workaround
Block Agent calls with isolation: \"worktree\" via a PreToolUse hook. The user had already written ~/.claude/hooks/block-worktree.sh for exactly this purpose; it just wasn't wired into settings.json at the time the incident happened. Wiring it now prevents recurrence.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗