Bash tool cwd does not persist across tool calls; hooks receive stale cwd instead of triggering command's actual directory

Open 💬 0 comments Opened Jul 11, 2026 by jlsport18

Summary

The Bash tool's working directory does not persist across separate tool calls within a
session, despite the tool's own description stating "The working directory persists
between commands, but shell state does not." Because plugin hooks (PostToolUse, Stop,
etc.) receive their cwd from this same session-level tracking rather than from the
actual directory a triggering Bash command executed in, any hook logic that shells out
based on cwd (most commonly git introspection) silently breaks — even though the
Bash command itself ran correctly against the intended directory.

Environment

  • Claude Code version: 2.1.207
  • OS: macOS 26.5.1 (BuildVersion 25F80)
  • Observed in a session using sandboxed Bash execution (default mode; not

dangerouslyDisableSandbox)

Steps to reproduce

  1. In one Bash tool call, run: cd /path/to/some/git/repo && pwd

→ prints the repo path, as expected.

  1. In a separate Bash tool call (same session), run: pwd

→ prints the session's original/default directory, not the repo path from step 1. The
tool output includes an explicit Shell cwd was reset to <default> notice.

  1. Trigger any installed plugin hook that reads cwd from its JSON input (e.g. a

PostToolUse hook matching on Bash(git commit:*)) via a compound command like
cd /path/to/repo && git commit -m "..." run in one Bash call.

  1. Inspect the hook's own logic/logging for the cwd value it received.

→ It is the same stale/default directory from step 2, not /path/to/repo — even
though the git commit inside the same Bash call succeeded against the correct
repo.

Expected behavior

Either:

  • The Bash tool's working directory persists across tool calls within a session, matching

its documented behavior, or

  • Hooks receive the actual working-directory context of the specific tool invocation that

triggered them, not a stale session-level default.

Actual behavior

  • cd does not persist between separate Bash tool calls; each call appears to reset to a

default directory.

  • Hooks are passed that same stale/default cwd, never the directory a compound

cd ... && <command> call actually executed the triggering command in.

Impact

Any plugin hook that depends on cwd to run its own git/filesystem introspection breaks
silently — the triggering command succeeds, but the hook's follow-up logic fails with
errors like fatal: not a git repository (or any of the parent directories): .git,
because it's operating against the wrong directory (observed values: /, a
/private/var/folders/.../T tmp dir, the user's home directory, and internal
subagent/workflow directories — never the actual repo). In one session, this produced
949 such failures in a single day across an officially-distributed marketplace plugin
(security-guidance, claude-plugins-official) whose commit-review, Stop-hook diff
review, and baseline-capture features all depend on cwd resolving to a real git repo.
No error is surfaced to the user or the model in this failure mode — the hook exits
quietly, so the review feature appears "enabled" while never actually running.

Suggested fix direction

Pass hooks the actual cwd the triggering tool call executed against (i.e., resolved
per-invocation, accounting for any cd within that same compound command), rather than a
session-level value that can go stale between calls. Alternatively, make Bash working-
directory persistence match the documented behavior so session-level cwd tracking stays
accurate.

View original on GitHub ↗