Windows: PreToolUse/PostToolUse/Stop hooks trigger at ~1.2% rate instead of 100%
Summary
PreToolUse, PostToolUse, and Stop hooks configured in settings.json successfully run (statusMessages appear in UI) but the hook scripts internal logic silently fails ~98.8% of the time. Hook scripts are syntactically valid and work correctly when invoked manually from terminal.
Environment
- OS: Windows 11 (zh-CN)
- Claude Code version: v2.1.167
- Python: 3.12
- Shell: Git Bash (MINGW64)
Configuration
Five hooks registered in .claude/settings.json:
"PreToolUse": [
{"matcher": "Read", "command": "python compliance_pre_write.py", "timeout": 30},
{"matcher": "Write|Edit", "command": "python compliance_pre_write.py", "timeout": 30},
{"matcher": "Bash", "command": "python compliance_bash_guard.py", "timeout": 5}
],
"Stop": [
{"matcher": "", "command": "python agent_quota_stop.py", "timeout": 10}
],
"PostToolUse": [
{"matcher": "Write|Edit", "command": "python audit_post_tool.py", "timeout": 5}
]
Expected Behavior
Each hook script should execute on every matching tool use:
compliance_pre_write→ every Write/Edit (~435 operations in test period)compliance_bash_guard→ every Bash (~50 operations)pre_read_guard→ every Read (~114 operations)agent_quota_stop→ every Stop event (~57 /ingest completions)audit_post_tool→ every Write/Edit completion (~435 operations)
Expected hook events recorded: ~1,091 total.
Actual Behavior
Only ~13 hook events recorded total across all hooks (~1.2% trigger rate).
The statusMessages do appear in the UI (e.g., "Pre-write compliance check...", "Checking search route..."), confirming the framework IS invoking the hooks. But the hook scripts internal logic (Event Store writes, JSONL logging) silently fails in ~98.8% of invocations.
Manual terminal invocation of the same scripts with the same arguments works correctly.
Diagnostic Evidence
- Hook scripts are valid: All 5 scripts pass Python
compile()syntax check - Hook scripts work from terminal: Manual execution writes to Event Store successfully
- statusMessages visible: Framework clearly detects and invokes hooks
- Event Store gap: 13 total hook-source events vs 1,091 expected
Root Cause Hypothesis
Three interacting factors on Windows:
- Subprocess CWD mismatch: Hook scripts run in a subprocess with CWD set by the framework (not necessarily vault root).
sys.pathmanipulation and relative imports fail silently.
- GBK/UTF-8 encoding conflict: Windows subprocess stdout defaults to GBK. Event Store Python scripts output UTF-8 with emoji characters →
UnicodeEncodeError→ subprocess exits abnormally → exit code lost.
- Timeout boundary:
compliance_bash_guard.py(5s timeout) internally calls external scripts (search_router.py) which may take 3-8s. Framework kills subprocess at timeout → exit code indeterminate → hook treated as "passed."
Workaround
We have moved critical enforcement from hooks to inline Claude tool calls (e.g., format_gate.py enforce --gate all called within Claude's own Bash invocations, which has 100% trigger rate). Hooks are now a secondary defense.