claude-code - 💡(How to fix) Fix Bash tool: CWD silently drifts into worktree subdirectories between calls (silent wrong-target git writes) [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#56147Fetched 2026-05-05 05:56:58
View on GitHub
Comments
1
Participants
2
Timeline
6
Reactions
0
Author
Timeline (top)
labeled ×5commented ×1

Error Message

In an interactive Claude Code session running a multi-step orchestration (specifically: a "dispatcher" Bash session that creates and removes git worktrees via the Agent(isolation: "worktree") API), the main session's Bash tool CWD silently drifts into one of the worktree subdirectories between two ordinary Bash calls. No cd was issued by the user or the model. The drift is invisible until a subsequent git invocation produces a wrong-target write — and that signal arrives several commands later, attached to an unrelated git checkout error. This is a silent wrong-target write failure mode. Git emits no error when CWD is inside a worktree owned by a different branch — it just operates against that worktree's HEAD. For orchestration workloads that issue write-class git commands (merge, push, pull, checkout, commit, tag, branch -D), the wrong-target write looks identical to a successful operation. The error only surfaces several commands later when an unrelated git checkout of the expected branch errors with '<branch>' is already checked out at '<parent-path>'.

Root Cause

This is a silent wrong-target write failure mode. Git emits no error when CWD is inside a worktree owned by a different branch — it just operates against that worktree's HEAD. For orchestration workloads that issue write-class git commands (merge, push, pull, checkout, commit, tag, branch -D), the wrong-target write looks identical to a successful operation. The error only surfaces several commands later when an unrelated git checkout of the expected branch errors with '<branch>' is already checked out at '<parent-path>'.

For sprint workflows that batch parallel agent dispatch behind a single git merge reconciliation pass, this silently merges agent work into the wrong destination branch and leaves the user's actual target branch missing the merges.

Fix Action

Fix / Workaround

In an interactive Claude Code session running a multi-step orchestration (specifically: a "dispatcher" Bash session that creates and removes git worktrees via the Agent(isolation: "worktree") API), the main session's Bash tool CWD silently drifts into one of the worktree subdirectories between two ordinary Bash calls. No cd was issued by the user or the model. The drift is invisible until a subsequent git invocation produces a wrong-target write — and that signal arrives several commands later, attached to an unrelated git checkout error.

Concrete trace from a real dispatcher session

The signal that the dispatcher operated on the wrong target arrives at t8, three commands after the wrong-target merges at t4 and t5. By then, two merge commits have been written to a worktree branch that the user expected to land on sprint/NOM910PHB.

RAW_BUFFERClick to expand / collapse

Preflight Checklist

  • I have searched existing issues and this hasn't been reported yet (closest matches noted under Related below — they describe adjacent CWD escapes but not the specific between-call drift documented here).
  • This is a single bug report.
  • I am using the latest version of Claude Code.

What's Wrong?

In an interactive Claude Code session running a multi-step orchestration (specifically: a "dispatcher" Bash session that creates and removes git worktrees via the Agent(isolation: "worktree") API), the main session's Bash tool CWD silently drifts into one of the worktree subdirectories between two ordinary Bash calls. No cd was issued by the user or the model. The drift is invisible until a subsequent git invocation produces a wrong-target write — and that signal arrives several commands later, attached to an unrelated git checkout error.

The Bash tool's documented contract is "the working directory persists between commands". That contract is violated here: the CWD changes between calls without any operation that should have moved it. Worse, the change is silent — there is no log line, no warning, no event surfaced to the model.

Concrete trace from a real dispatcher session

StepActionObserved state
t0Session at parent repo /home/.../take-me-out. Branch sprint/NOM910PHB. pwd returns parent.Normal.
t1Spawn Agent(isolation: "worktree") executor. Runtime creates .claude/worktrees/agent-a435b32aa9e5ef96b/ and branch worktree-agent-a435b32aa9e5ef96b.Normal.
t2Spawn second Agent(isolation: "worktree") executor in a separate worktree.Normal.
t3Both executors complete. Run cleanup git/git worktree remove calls in Bash.The Bash shell's CWD has silently moved into .claude/worktrees/agent-a435b32aa9e5ef96b/. No cd was issued.
t4git merge worktree-agent-<other> --no-editOutput: Merge made by 'ort' strategy. Expected fast-forward (the other worktree branch is one commit ahead of the parent's sprint HEAD). The 3-way merge is the first signal — easily attributed to a parallel-merge artefact and missed.
t5git merge worktree-agent-a435b32aa9e5ef96b --no-editOutput: Already up to date. Second signal — that worktree branch is not sprint's ancestor, so a real merge was expected.
t6git log --oneline -5Top commit names the worktree branch as the merge destination, not sprint. The merge landed inside the worktree, not on sprint.
t7git branch --show-currentReturns worktree-agent-a435b32aa9e5ef96b. Confirms the shell was inside the worktree at t4-t5.
t8git checkout sprint/NOM910PHBfatal: 'sprint/NOM910PHB' is already checked out at '/home/.../take-me-out'. Correct — the parent IS on sprint. The shell is just inside a worktree dir.
t9pwd/home/.../take-me-out. CWD has now moved back on its own, between t7 and t9, with no cd in between.

The signal that the dispatcher operated on the wrong target arrives at t8, three commands after the wrong-target merges at t4 and t5. By then, two merge commits have been written to a worktree branch that the user expected to land on sprint/NOM910PHB.

What Should Happen?

The Bash tool's CWD should not silently change between calls. If a worktree-create or worktree-remove operation has a side effect that moves the shell CWD, that side effect should:

  1. Be loud. Surface a log line or event the model can observe.
  2. Be deterministic. Document which operations move the CWD and to what target.
  3. Or — preferably — not happen at all. The Bash tool should pin the CWD to the value the model last set (or the session's launch directory) and not let other tool invocations rewrite it.

If full pinning is not feasible, a defensive shape would be: when git is invoked and the CWD is inside a worktree directory whose .git file points to a .git/worktrees/<id>/ of the session's launch repo, surface a warning ("CWD is inside a worktree of the launch repo; git operations will target the worktree branch, not the parent's checked-out branch — proceed?").

Steps to Reproduce

  1. Open Claude Code in a project that is a git repository.
  2. From the project root, ask the model to run a few cycles of: spawn an Agent(isolation: "worktree"), wait for completion, then run git worktree remove --force <other-worktree> and git branch -D <other-branch> for an unrelated worktree (i.e., not the one the spawned agent lives in).
  3. After 3-5 such cycles, ask the model to run pwd and git branch --show-current.
  4. Observe the CWD has likely moved into one of the worktree directories, and git invocations now operate against that worktree's HEAD rather than the parent's.

Why this matters

This is a silent wrong-target write failure mode. Git emits no error when CWD is inside a worktree owned by a different branch — it just operates against that worktree's HEAD. For orchestration workloads that issue write-class git commands (merge, push, pull, checkout, commit, tag, branch -D), the wrong-target write looks identical to a successful operation. The error only surfaces several commands later when an unrelated git checkout of the expected branch errors with '<branch>' is already checked out at '<parent-path>'.

For sprint workflows that batch parallel agent dispatch behind a single git merge reconciliation pass, this silently merges agent work into the wrong destination branch and leaves the user's actual target branch missing the merges.

Workaround in place (defensive coding)

Downstream, we are pinning every git invocation in our dispatcher SKILLs to an explicit work tree via git -C "$(dirname "$(git rev-parse --path-format=absolute --git-common-dir)")" <subcommand>. The expression resolves to the parent repo's working-tree root regardless of where the shell's CWD has drifted, because git rev-parse --git-common-dir returns the path to the main repo's .git, not the per-worktree .git/worktrees/<id>/ directory.

This works as a defensive shape, but it is a forever-tax on every git invocation in every orchestration SKILL — and it leaves CWD-sensitive non-git tools (Edit with absolute paths into the parent prefix, MCP tools rooted at the parent) without an analogous defence.

The full architectural decision and rollout for the workaround is documented at:

If/when the upstream Bash CWD drift is fixed, ADR-051's mitigation can be downgraded from enforce to advisory after two consecutive Claude Code releases without regression. The ADR's revisit triggers explicitly cite this issue ID as the observable referent.

Related

  • #40968 (OPEN) — "Claude Code spawns unwanted git worktrees and gets stuck in deleted worktree CWD". Adjacent: that report describes CWD locked to a deleted worktree path. This report describes CWD silently moving into an existing worktree subdirectory between calls.
  • #42034 (OPEN) — "SDK sessions escape cwd and modify parent repository when running in git worktrees". Adjacent: that report describes SDK sessions escaping outward from worktree-scoped cwd to the parent. This report describes the inverse — interactive sessions launched at the parent drifting inward into worktrees.
  • #50960 (OPEN) — "CLI hooks: process CWD drifts away from project root mid-session". Adjacent: that report describes hook-context CWD drift. This report describes Bash-tool CWD drift between calls in the main interactive session.
  • #27876 (CLOSED) — established harness-defect precedent that motivated our existing WorktreeCreate hook (different bug, same harness layer).

Environment

  • Claude Code: latest at the time of trace capture (2026-05-03).
  • Platform: Linux 6.6.99-09000-gd3ae1caecf39.
  • Repo: gitban-mcp dispatcher SKILL using Agent(isolation: "worktree") for parallel sprint-card execution.

Happy to run diagnostics — e.g., a pwd-and-git rev-parse --show-toplevel check inserted between every Bash call across a full dispatch cycle — if that would help isolate which exact operation moves the CWD.

extent analysis

TL;DR

The Bash tool's CWD silently changes between calls when using Agent(isolation: "worktree"), causing wrong-target writes, and a fix or workaround is needed to prevent this issue.

Guidance

  • Investigate the Agent(isolation: "worktree") API to determine if it has any side effects that could cause the CWD to change.
  • Add logging or debugging statements to track the CWD between Bash calls to identify when and why it changes.
  • Consider implementing a workaround like the one described in the issue, using git -C "$(dirname "$(git rev-parse --path-format=absolute --git-common-dir)")" <subcommand> to pin git invocations to the parent repo's working-tree root.
  • Review related issues (#40968, #42034, #50960) to see if they provide any insight into the cause of the problem.

Example

# Pin git invocations to the parent repo's working-tree root
git -C "$(dirname "$(git rev-parse --path-format=absolute --git-common-dir)")" merge worktree-agent-<other>

Notes

The issue is specific to the Agent(isolation: "worktree") API and the Bash tool's CWD behavior, so any solution or workaround will need to take these factors into account. The provided workaround is a defensive shape that can help prevent wrong-target writes, but it may not be the most efficient or elegant solution.

Recommendation

Apply the workaround using git -C "$(dirname "$(git rev-parse --path-format=absolute --git-common-dir)")" <subcommand> to pin git invocations to the parent repo's working-tree root, as it provides a reliable way to prevent wrong-target writes until a more permanent fix is available.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix Bash tool: CWD silently drifts into worktree subdirectories between calls (silent wrong-target git writes) [1 comments, 2 participants]