PreToolUse hook with relative path breaks all tool calls when Bash 'cd' shifts CWD

Resolved 💬 3 comments Opened Mar 9, 2026 by dstreefkerk Closed Mar 12, 2026

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

  1. Register a PreToolUse hook in settings.local.json using a relative path:

``json
{
"hooks": {
"PreToolUse": [{
"hooks": [{
"type": "command",
"command": "python .claude/hooks/batch-progress-logger.py"
}]
}]
}
}
``

  1. Have Claude execute a Bash command that ends in a subdirectory:

``bash
mkdir -p analyst-ui && cd analyst-ui && dotnet new ...
``

  1. The shell's CWD is now analyst-ui/ (CWD persists between Bash calls per design).
  2. 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.json to 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.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗