[Bug] Session-wide shared cwd slot: concurrent subagents (with or without EnterWorktree) silently repoint every shell in the session tree — cross-worktree mutation risk (fresh report per stale-closed #42282)

Open 💬 1 comment Opened Jul 10, 2026 by jaredmiller23

Environment

  • Claude Code CLI 2.1.201
  • macOS (Darwin 25.5.0)
  • Session shape: one orchestrator session dispatching multiple concurrent subagents (via the Agent tool, foreground and run_in_background), each subagent working its own git worktree under .claude/worktrees/<name>, created via git worktree add and then bound with EnterWorktree(path: <worktree>)
  • Repo: a Next.js/TypeScript monorepo using git worktrees as the standard per-lane isolation mechanism, with a repo-local PreToolUse hook that gates mutating Bash/Edit/Write calls on repo custody state (not itself the bug, but it's how several bleeds were first detected — a command reporting the wrong branch)

Summary

EnterWorktree(path: …) is documented (and reasonably assumed) to rebind only the calling agent's own working-directory context. In practice, under concurrency, it behaves like a single mutable slot shared by the whole session tree: when any agent — a subagent, or even a subagent several dispatch-levels deep — calls EnterWorktree(path:), it silently repoints the cwd seen by every other live shell in that session, including the orchestrator's own persistent shell and sibling subagents' shells. The tool's contract implies isolation; the observed behavior is global, last-writer-wins mutation.

This has been reproduced repeatedly (~43 logged incidents across many sessions over roughly a week of heavy worktree-based fleet usage) and is the single highest-frequency, highest-severity harness friction item we've logged for this workflow.

Expected behavior

Each agent (orchestrator and every subagent) that calls EnterWorktree(path:) should get its own cwd binding, independent of what any other concurrently running agent in the same session does. A parent session's shell should never be silently relocated by a child subagent's worktree operations, and two sibling subagents should never observe each other's cwd.

Actual behavior

  • Any EnterWorktree(path:) call, by any agent, clobbers one session-wide cwd slot.
  • Every shell in the session tree — including the orchestrator's — re-reads that slot on its next Bash call, surfacing as an unprompted "Shell cwd was reset to <newest lane's worktree>" notice.
  • A bare cd back to the intended directory does not stick once the slot has been externally pinned by another agent's EnterWorktree call — the next tool call snaps back to whichever worktree was most recently entered by anyone.
  • This is bidirectional and not limited to parent→child: sibling subagent A can end up executing inside sibling subagent B's worktree, and vice versa, when both bootstrap around the same time.
  • The bleed does not require any EnterWorktree call at all: in a 2026-07-09 session with three concurrent Agent-tool lanes and zero EnterWorktree invocations that turn, the orchestrator's shell was still repeatedly reset into a lane's worktree between tool calls. Concurrent subagent activity alone suffices, which suggests the shared slot is written by the subagent bootstrap/teardown path, not only by the explicit tool.
  • The failure is invisible until something downstream contradicts expectation — a git status/git branch --show-current showing the wrong branch, a custody/intake tool reporting a foreign lane's state, a git add <path> failing on an unexpected pathspec, or file content not matching what was just written.

Minimal reproduction

  1. Start an orchestrator session in a git repo that uses worktrees for lane isolation.
  2. From the orchestrator, dispatch two or more subagents concurrently (single message, multiple Agent tool calls, or run_in_background: true), each instructed to:
  • git worktree add <own-worktree-path> -b <own-branch> origin/main
  • EnterWorktree(path: <own-worktree-path>)
  • do some file work and eventually commit
  1. While those subagents are bootstrapping, have the orchestrator (or another subagent) run any Bash command, or itself call EnterWorktree on a different path.
  2. Immediately run pwd and git branch --show-current from the orchestrator's own shell, and/or inspect what a subagent's next Bash call actually operated on (e.g., which branch a git commit landed on, or which worktree a git add/staging step touched).

Observed: the orchestrator's shell (or a sibling subagent's shell) is now pinned to whatever worktree the most recent EnterWorktree(path:) call — from any agent — targeted. Mutating commands issued in that window execute against the wrong lane's working tree, not the caller's own.

Concrete incidents observed

These are generalized from a working week of fleet-style dispatch (multiple concurrent subagents per session, each in its own worktree) on one repository:

  • The orchestrator's own persistent shell was repinned into a subagent's active worktree on at least a dozen separate occasions across different sessions, each time surfacing as an unprompted "Shell cwd was reset to …" message or a pwd/branch mismatch caught just before a mutating command.
  • On one occasion the orchestrator's own git fetch + git merge origin/main --no-edit executed inside a live subagent's worktree, over that subagent's uncommitted changes — the merge itself was benign in content, but it invalidated the subagent's in-flight custody/commit state and had to be manually recovered.
  • On another occasion the orchestrator's npm ci executed inside a concurrently-running subagent's worktree while that subagent's own typecheck was in flight, producing a "bizarre transient" pre-push typecheck failure (missing subpath resolution, a stray parse error) that vanished on retry — consistent with node_modules being rewritten out from under a running tsc process by an unrelated agent's install.
  • Two sibling subagents bootstrapping at the same time repeatedly cross-contaminated: subagent A's bootstrap commands (e.g. git branch --set-upstream-to=origin/main) executed against subagent B's worktree and retargeted B's branch tracking config, discovered only because the reported branch name in a status/intake step didn't match the branch the calling agent had been assigned.
  • A repo-local commit-gating hook that derives repo state from the session cwd granted a "clear to mutate" verdict for the wrong worktree/branch during a bleed window — i.e., the bleed is invisible to and unguarded by tooling that trusts the harness-reported cwd, because that cwd itself is the compromised value.
  • A one-off but severe variant: after an ExitWorktree/EnterWorktree(path) pair, the primary (root) checkout's git config was found with core.bare=true moments later, with the timestamp landing within the same second as the worktree-switch call — strongly suggesting the native worktree-switch machinery itself writes into the primary repo's shared config as a side effect of the switch, not just moving a logical cwd pointer. (This overlaps with the already-filed core.bare corruption reports below but was observed in the same incident cluster.)

None of these produced permanent data loss in our case (recoveries relied on everything being committed/pushed independently, or on catching the drift before a destructive command ran), but the mechanism clearly permits silent cross-lane corruption, including commits landing on the wrong branch and uncommitted work being at risk from a sibling lane's broad-staging command.

Impact

  • Silent cross-lane mutation: commands intended for one lane execute against another lane's working tree, with no error or warning.
  • Broken tooling that trusts cwd: any repo-local automation (hooks, custody/intake scripts, CI-adjacent local gates) that derives "which branch/lane am I in" from the session's reported cwd inherits the bleed and can issue verdicts for the wrong lane.
  • Recovery tax: the only reliable detection is manual — pwd plus git branch --show-current before every mutating command, in every agent, for the lifetime of any session running concurrent worktree-bound agents. This is a real, ongoing cost when the correct behavior (per-agent cwd isolation) is what the tool's contract already promises.
  • Compounding with ExitWorktree: a session that was launched pinned to worktree A and later EnterWorktree'd into worktree B will, on ExitWorktree(remove), reap/remove worktree A (its original launch directory) rather than B — a related but distinct bug where "return to origin" semantics track launch state, not current state, compounding the confusion once a bleed has already happened.

Workarounds currently in use

  • pwd && git branch --show-current immediately before every mutating step, in every agent (orchestrator included) — catches essentially 100% of bleeds before they cause damage, at the cost of one extra round-trip per mutation.
  • Prefer absolute paths and git -C <absolute-worktree-path> for every operation rather than relying on the ambient shell cwd at all.
  • Never issue a corrective EnterWorktree/ExitWorktree call while a sibling lane is actively mid-mutation — doing so yanks that lane's cwd out from under it.
  • Never dispatch a new subagent lane into the orchestrator's own currently-pinned worktree (a subagent spawned into a live lane inherits that lane's dirty state as its starting cwd, which is a related but separate hazard).
  • A lane whose deliverable has already been pushed to its remote should not re-enter its own worktree "just to fix cwd" — better to leave it alone and report status than risk perturbing a sibling's active session.
  • Stagger subagent bootstraps rather than launching them all in the same instant, and keep concurrent lane counts low; this narrows but does not close the race window.

Suggested fix direction

The tool's own documentation states cwd binding "affects only this agent." Given the reproducibility here, either:

  1. that documentation is aspirational and the implementation actually maintains one process-wide (or session-wide) cwd rather than a per-agent-id registry, or
  2. there is a per-agent registry, but something (shared shell state, a shared environment variable, a race in how the shell is spawned/re-attached per tool call) causes it to be overwritten cross-agent.

A per-agent (or per-subagent-invocation) cwd registry, keyed by agent/session id rather than a single global pointer, would resolve the isolation gap directly. Failing that, the tool should at minimum surface a loud, per-call warning any time a cwd it's about to use was last set by a different agent than the one currently issuing the command — this alone would turn today's silent corruption into a visible, catchable event.

Related issues (found via search, none an exact duplicate)

  • #42282 (closed NOT_PLANNED — the closest prior art; verified 2026-07-09) — "Sub-agents in worktrees cause persistent working directory drift." Closed as fixed in v2.1.97, then reproduced by commenters on v2.1.114 (2026-04-18) and v2.1.166 (2026-06-03→06) with minimal repros matching this report's mechanism (parent cwd silently moves into a subagent's worktree, including with pre-created worktrees and NO isolation parameter), then auto-closed by the stale-bot with "open a new issue if still relevant." This report is that new issue; our receipts extend the evidence to ~39 incidents across 6+ sessions on v2.1.1xx–2.1.2xx incl. parent/orchestrator-side strikes, cross-lane gate-receipt corruption, and stale Read/Edit snapshots.
  • #27881 (referenced by #42282) — post-compaction cwd drift; same family, narrower trigger.
  • #67725 (open) — "Bash tool: shell cwd resets to project root between tool calls, breaks worktree workflows" — same general class (cwd instability across tool calls) but describes reset-to-root, not cross-agent bleed into a sibling's worktree.
  • #60896 (closed) — "One session's HEAD silently changes when another session in the same repo creates/modifies a worktree" — same underlying theme (shared git state leaking across supposedly-isolated contexts) but framed as separate Claude Code sessions sharing a repo, not an orchestrator and its own concurrent subagents within one session.
  • #32815 (closed, COMPLETED) — "Fellowship: quest runner CWD can land in wrong worktree when spawned in parallel" — a plugin-specific report of the same symptom (parallel agents, CWD lands in the wrong worktree), closed without an evident generalized fix, which this report's evidence suggests may still be live at the harness level.
  • #74386 (open) — "Worktree cleanup can discard another session's in-progress work — no liveness signal, only git state" — adjacent hazard in the same worktree-lifecycle area.
  • #69802 (open) — "ExitWorktree (remove) reports success but orphans the worktree (dir + admin entry + branch); intermittently corrupts parent repo core.bare" — overlaps with the core.bare side-effect noted above.
  • #66299 (open) — "Permission inheritance not re-resolved when worktree created after main-tree work" — adjacent worktree-lifecycle gap, different symptom.

If maintainers confirm one of the above is actually the same root cause, this report should be merged into it; from the available issue text none of them captures the specific "single shared cwd slot, any concurrent agent can clobber it, including the parent" mechanism described here.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗