[BUG] Bash hooks (PreToolUse/PostToolUse) silently not invoked on Windows with settings.local.json-only config
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
PreToolUse and PostToolUse Bash hooks configured in .claude/settings.local.json are never invoked on Windows. No Bash tool call triggers the configured
scripts, despite a full Claude Code restart from the project root. The hook subprocess appears to never be spawned: a trace echo added as the first
executable line of the hook script never writes to disk after Bash tool calls.
What Should Happen?
PreToolUse Bash hook should run before every Bash tool call; PostToolUse Bash hook should run after. Both should have their subprocess spawned and their
stdin receive the JSON tool input, regardless of whether there is a sibling settings.json file in the project.
Error Messages/Logs
Evidence collected (what I already ruled out):
1. Script correctness — invoked directly with a JSON stub it works as intended:
$ echo '{"tool_input":{"command":"env | head"}}' | "C:\PROGRA~1\Git\usr\bin\bash.exe" /c/myproject/.claude/hooks/my-precheck.sh
BLOCKED: <expected message>
(exit 1)
2. Trace probe — the hook script has this as its first executable line:
echo "$(date -u) pid=$$" >> /c/myproject/.claude/hooks/precheck.trace
After many Bash tool calls post-restart, the trace file does not exist.
The hook process is never spawned.
3. Post-hook log frozen — the PostToolUse hook appends to a log on every call.
mtime stays at the pre-restart timestamp; zero new entries despite dozens of
Bash tool calls since restart.
4. CC does track hooks in principle — ~/.claude.json contains
pre_tool_hook_duration_ms_count counters with non-zero histories from other
sessions, so the feature is wired at the client level, just not firing for
this config.
5. Tooling verified — jq 1.8.1 in PATH, bash path C:\Program Files\Git\usr\bin\bash.exe
resolves, short form C:\PROGRA~1\Git\usr\bin\bash.exe also resolves, forward-slash
script paths (/c/...) work when called manually.
No hook-related errors surface anywhere visible to me (transcript, CC UI, any log file I could find).
---
Steps to Reproduce (campo reproduction):
1. On Windows with Git Bash installed, create a project with **only** `.claude/settings.local.json`
(no sibling `settings.json`, no user-global `~/.claude/settings.json`). Content:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "C:\\PROGRA~1\\Git\\usr\\bin\\bash.exe /c/tmp/trace.sh",
"timeout": 5
}
]
}
]
}
}
2. Create C:\tmp\trace.sh:
#!/usr/bin/env bash
echo "fired $(date -u +%s) pid=$$" >> /c/tmp/cc-hook-trace.log
exit 0
3. Fully close Claude Code, reopen from the project root.
4. Issue any Bash tool call in the session (e.g. ask Claude to run `ls`).
5. Observe: /c/tmp/cc-hook-trace.log is never created.
Expected: the file exists with one `fired ...` line per Bash tool call.
Actual: the file is never created. The hook subprocess is never spawned.
Steps to Reproduce
- On Windows with Git Bash installed, create a project with only
.claude/settings.local.json
(no sibling settings.json, no user-global ~/.claude/settings.json). Content:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "C:\\PROGRA~1\\Git\\usr\\bin\\bash.exe /c/tmp/trace.sh",
"timeout": 5
}
]
}
]
}
}
- Create C:\tmp\trace.sh:
#!/usr/bin/env bash
echo "fired $(date -u +%s) pid=$$" >> /c/tmp/cc-hook-trace.log
exit 0
- Fully close Claude Code, reopen from the project root.
- Issue any Bash tool call in the session (e.g. ask Claude to run
ls).
- Observe: /c/tmp/cc-hook-trace.log is never created.
Expected: the file exists with one fired ... line per Bash tool call.
Actual: the file is never created. The hook subprocess is never spawned.
Claude Model
Opus
Is this a regression?
Yes, this worked in a previous version
Last Working Version
_No response_
Claude Code Version
2.1.114
Platform
Other
Operating System
Windows
Terminal/Shell
Other
Additional Information
- Is settings.local.json alone sufficient on Windows for hook registration, or is a sibling settings.json required even if the docs don't state it?
- Is there a CC debug flag, env var, or log file that exposes whether the hook subprocess was attempted (and its stdout/stderr/exit) — analogous to the
pre_tool_hook_duration_ms telemetry in ~/.claude.json, but with per-invocation records?
- Any known Windows-specific gotcha with the "command" field format (quoting, spaces in Program Files, short-name path PROGRA~1)?
Happy to provide more detail on request.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗