PreToolUse hooks never fire despite trusted workspace and correct config (VS Code extension + bundled CLI, v2.1.220, Windows)
Bug report: PreToolUse hooks never fire despite correct config and accepted workspace trust
Summary
PreToolUse hooks defined in a committed .claude/settings.json never execute — not even once — even though:
- The workspace trust flag is confirmed
true - The hook script runs correctly when invoked manually (outside the hook mechanism)
- The behavior reproduces identically in both the VS Code native extension and the standalone CLI binary bundled with it
This affects a security-relevant use case (a commit-gating audit tool), so silent, unexplained non-execution is a serious problem.
Environment
- Claude Code VS Code extension version:
2.1.220(win32-x64) - Also reproduced with:
resources/native-binary/claude.exefrom the same extension package, launched from an independent terminal (fully outside the VS Code process tree) - OS: Windows 11 Home 10.0.26200
- Shell: PowerShell 5.1 / Git Bash
.claude/settings.json (relevant excerpt)
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash(git commit*)",
"hooks": [{ "type": "command", "command": "node tools/auth-in-ai-loop/dist/hooks/pre-commit-audit.js --dry-run" }]
},
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "node tools/auth-in-ai-loop/dist/hooks/bash-command-guard.js --dry-run" }]
}
]
}
}
Steps to reproduce
- Have a trusted workspace (
~/.claude.json→projects["<path>"].hasTrustDialogAccepted: true, confirmed via direct file read). - Have the above hooks correctly wired in a committed
.claude/settings.json. - Ask Claude (via its own Bash tool) to run
git commit --allow-empty -m "test"inside the repo. - Observe: the
git commit*-matched hook never runs (no side effect it's supposed to produce — e.g., no log file write). - Separately, ask Claude to run any Bash command containing a heredoc (e.g.
cat <<'EOF' ... EOF), which the generic"matcher": "Bash"hook is written to flag on stderr in dry-run mode. No such output ever appears, for any Bash command, across dozens of invocations in the session.
What we ruled out (each independently tested)
- Session-level config caching: retested in a brand-new chat conversation — still no firing.
- Extension-host-level caching: retested after a full VS Code restart and a full Windows restart (brand-new session) — still no firing.
- Trust flag not actually applied: re-read
~/.claude.jsondirectly each time;hasTrustDialogAcceptedwas confirmedtruethroughout all tests. - Duplicate/conflicting project entries in
~/.claude.json(e.g. path casing/slash mismatches): none found. - Overriding local/user settings (
.claude/settings.local.json,~/.claude/settings.json) disabling or shadowing the hooks: neither contains anyhookskey; only unrelatedpermissionsentries. - Compound-command matcher mismatch (hypothesis:
cd X && git commit ...doesn't matchBash(git commit*)because Claude Code decomposes compound commands for permission matching but maybe not for hook matching): tested withcd X && git commit ..., and with a standalonegit commit ...(no compound command at all) — neither fired the hook. - Relative-path resolution issue (hook command is a relative path
node tools/.../foo.js): confirmed via the VS Code extension's own hooks documentation that hook processes run with cwd = project root regardless of the Bash tool'scd, so this shouldn't matter; also empirically no difference across cwd variations. - Hook process starts but crashes before completing (there's a separate, real, unrelated Windows-specific Node/libuv crash —
Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\win\async.c, line 94— observed when running the hook script manually): ruled out as the cause here. We added a synchronous diagnostic write (fs.appendFileSync) as the very first line of the hook script'smain(), rebuilt, and ran the samegit committest. The diagnostic file was never created at all — i.e., the hook process is not being spawned in the first place; it's not a case of "starts then dies." - VS Code extension host specifically being the faulty layer: retested with the CLI binary bundled inside the same extension package (
resources/native-binary/claude.exe), launched from a terminal completely independent of VS Code's process tree, with Claude (inside that CLI session) itself issuing thegit commitvia its own Bash tool (confirmed — not manually typed by the human into the shell). Same result: no hook firing. - Not the same failure mode as #4362 (
approve: falsebeing ignored) or thepermissionDecisionoutput-format deprecation: those describe the hook firing but its block decision being ignored/misformatted. Our hooks run with--dry-run(never intended to block), and the diagnostic-marker test above proves the process isn't even started, which is a strictly earlier failure point than either of those known issues.
Attempted to get more signal via --debug, no luck
Tried running the CLI binary with claude --debug and capturing output two ways:
Start-Transcriptaround the interactive session — captured nothing useful (PowerShell's transcript can't capture a full-screen/raw-mode TUI's rendered content).claude --debug 2> logfile.log(stderr-only redirect, leaving stdout/TUI on the console) — the log file was created but stayed completely empty.
Neither method surfaced any debug output related to hook dispatch, or any output at all. Not sure if --debug writes to a different sink in this build, or if it's simply not emitting anything for this code path.
Cumulative reproduction count
Across this investigation we made 7 separate test commits (git commit --allow-empty) via Claude's own Bash tool, covering: VS Code extension (fresh chat session, fresh chat + full VS Code/Windows restart, compound cd && git commit, standalone git commit, with a startup diagnostic marker instrumented into the hook script) and the standalone CLI binary (plain run, --debug with transcript capture, --debug with stderr redirect). Every single one failed to trigger the hook (verified via the hook's own log file, which the hook script writes to on successful invocation, and via the diagnostic marker file in one case).
Conclusion
The PreToolUse hook mechanism appears not to be invoking the configured hook process at all, for any matcher (Bash(git commit*) and the broader Bash), despite a trusted workspace and syntactically correct, committed configuration. This reproduces identically in both the VS Code extension and its bundled standalone CLI binary, so it does not appear to be specific to the IDE integration layer. We were unable to narrow the cause further from inside a Claude Code session, since we cannot introspect our own host process's hook-dispatch logic, and --debug did not surface any additional signal.
Request
- Any known issue matching this? A pointer to a workaround or a fix version would be very helpful.
- If useful, we can share the exact (redacted)
.claude/settings.jsonand hook script for repro.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗