Session cwd silently resets to original launch directory, discarding legitimate navigation (affects PreToolUse hook cwd for all tool types)

Status Open
Reported on v2.1.219
Maintainer reply None cached
Activity 0 comments · opened Aug 3, 2026

Summary

A session's tracked working directory can silently drift back to its original launch directory, discarding all subsequent legitimate cd navigation — and this same stale/drifted cwd is what gets passed to PreToolUse hooks for any tool type (not just Bash), causing hooks that gate on "what repo/branch am I in" to enforce against the wrong repository entirely.

Impact

We run a PreToolUse hook (a small Python script invoked via settings.json) that blocks Write/Edit/Bash calls on Jira-ticket-keyed git branches unless a claim receipt exists. It resolves "what branch is this operation happening against" from the cwd field in the hook's stdin JSON. When session cwd has silently drifted to an unrelated sibling repo, the hook enforces against that repo's checked-out branch instead of the branch the user believes they're on — producing a confusing, byte-identical-looking false-positive block that cites a completely unrelated ticket/branch.

More generally: any tooling (hooks, or an agent's own directory bookkeeping) that trusts the harness's reported cwd as authoritative for "where is this session working" can be misled by this drift.

Evidence #1 — original incident (transcript)

Session transcript (local project dir -Users-kicksaw-Claude-Code-sf-architect--claude-worktrees-git-local-sync-dc02d2), working in a git worktree for repo A (sf-architect, branch claude/git-local-sync-dc02d2):

  1. A sequence of Bash tool calls each ran cd /path/to/repo-B/...; <read-only command> to inspect reference docs living in a different, unrelated sibling repo (internal-plugins). Each cd was to a deeper subdirectory of repo B (e.g. internal-plugins/plugins/sf-architect/skills/sf-architect/references/pipeline/qa-fix).
  2. No command ever cd'd back to repo A.
  3. The next tool call — a Write, not Bash — was blocked by our PreToolUse hook citing repo B's currently-checked-out branch (a keyed branch belonging to a completely different, unrelated ticket).
  4. The blocked call's own result metadata contained both:
  • "cwd": ".../internal-plugins/plugins/sf-architect/skills/sf-architect/references/pipeline/qa-fix" (repo B, several directories deep)
  • "gitBranch": "claude/git-local-sync-dc02d2" (repo A's actual branch — correct!)

in the same result object — i.e. the harness's own gitBranch metadata still knew the true branch, while the cwd value handed to the hook (and presumably to the Write tool's own resolution logic) had drifted to the unrelated repo.

Evidence #2 — live reproduction (this session)

In a fresh session, after cd-ing from the session's original launch directory into an unrelated repo and doing ~40 tool calls of legitimate work there (cwd persisted correctly across all of them, as expected for a Bash-tool-like persistent shell):

cd /tmp && pwd

printed /tmp as expected, but immediately after, the harness appended:

Shell cwd was reset to /Users/<user>/<original-session-launch-directory>

A follow-up pwd confirmed the cwd was now back at the original launch directory — not the repo I had legitimately been working in for the prior ~40 tool calls. All of that intermediate, legitimate navigation was silently discarded.

Evidence #3 — recurrence without an out-of-bounds cd

Later in the same session, after cd-ing back into the legitimate working repo and continuing normal work (including one aborted tool call and a user-message turn boundary), a plain pwd check showed the cwd had drifted back to the original launch directory again — with no explicit out-of-bounds cd performed in between. This suggests the reset/drift is not only triggered by a cd outside some allowed-directory set; it can also recur across tool-call-error or turn boundaries.

Expected behavior

  • If cwd tracking enforces an allowed-directory boundary, a cd that legitimately moved the session's working directory to a new location (as evidence #2 shows normally works fine) should update the "authoritative" cwd the harness resets to — not silently reset all the way back to the session's original launch directory.
  • Whatever cwd value is fed to PreToolUse hooks (for Write/Edit/Bash/etc.) should match whatever gitBranch/other session metadata reports, so hook authors don't have to guard against these two signals disagreeing within the same tool call's metadata.

Environment

  • Claude Code version: 2.1.219 (from transcript metadata; evidence #2/#3 gathered on a more recent version)
  • macOS (Darwin)
  • Entry point: claude-desktop / CLI (both evidenced)

Reproduction steps (evidence #2/#3 class)

  1. Start a Claude Code session in directory A.
  2. Ask it to cd into an unrelated directory B and do several tool calls there (confirm cwd persists correctly).
  3. Ask it to run a command that cds outside whatever the harness's allowed-directory set is (e.g. cd /tmp && pwd).
  4. Observe: cwd is reset, but to directory A (the original launch dir), not directory B (the last legitimate working directory).

View original on GitHub ↗