PreToolUse hook with relative path breaks all tool calls when Bash 'cd' shifts CWD
Summary
Registering a PreToolUse hook with a relative path (e.g. python .claude/hooks/my-hook.py) causes all subsequent tool calls to fail if a Bash tool call containing a cd command shifts the shell's working directory away from the project root.
Steps to Reproduce
- Register a PreToolUse hook in
settings.local.jsonusing a relative path:
``json``
{
"hooks": {
"PreToolUse": [{
"hooks": [{
"type": "command",
"command": "python .claude/hooks/batch-progress-logger.py"
}]
}]
}
}
- Have Claude execute a Bash command that ends in a subdirectory:
``bash``
mkdir -p analyst-ui && cd analyst-ui && dotnet new ...
- The shell's CWD is now
analyst-ui/(CWD persists between Bash calls per design). - Every subsequent tool call (Bash, Read, Glob, Write, Agent, etc.) immediately fails with:
````
PreToolUse:Bash hook error: [python .claude/hooks/batch-progress-logger.py]:
C:\Python312\python.exe: can't open file
'C:\Repos\my-project\analyst-ui\.claude\hooks\batch-progress-logger.py':
[Errno 2] No such file or directory
Root Cause
Claude Code resolves hook command strings relative to the current working directory at hook invocation time, not the project root. Because CWD persists between Bash tool calls, a cd into a subdirectory permanently shifts resolution for all subsequent hook invocations in that session.
Impact
- The session becomes completely unusable — every tool call is blocked by the hook error.
- The user must manually edit
settings.local.jsonto remove the hook entry to recover. - The hook itself is a no-op when its target env var is absent (
sys.exit(0)) — but it never reaches that code because Python can't locate the file.
Workaround
Use git rev-parse --show-toplevel in the hook command to resolve the project root regardless of CWD:
{
"command": "bash -c 'python \"$(git rev-parse --show-toplevel)/.claude/hooks/batch-progress-logger.py\"'"
}
Suggested Fix
Claude Code should resolve hook command paths relative to the project root (the directory containing .claude/), not the shell's current working directory. This matches user expectations and is consistent with how settings.json and settings.local.json themselves are located.
Alternatively, document clearly that hook paths are CWD-relative and warn users to use absolute or git-root-relative paths.
Environment
- Platform: Windows 11 Enterprise
- Shell: bash (Git Bash via Claude Code)
- Claude Code version: (current)
Edit: Claude Code went and opened this issue here instead of logging it against my own project, but I'm keen to see if there's a solution instead of the kludgy workaround it has proposed.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗