Windows: PreToolUse/PostToolUse/Stop hooks trigger at ~1.2% rate instead of 100%

Open 💬 0 comments Opened Jun 17, 2026 by lzllget5154321-gif

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

  1. Hook scripts are valid: All 5 scripts pass Python compile() syntax check
  2. Hook scripts work from terminal: Manual execution writes to Event Store successfully
  3. statusMessages visible: Framework clearly detects and invokes hooks
  4. Event Store gap: 13 total hook-source events vs 1,091 expected

Root Cause Hypothesis

Three interacting factors on Windows:

  1. Subprocess CWD mismatch: Hook scripts run in a subprocess with CWD set by the framework (not necessarily vault root). sys.path manipulation and relative imports fail silently.
  1. 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.
  1. 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.

View original on GitHub ↗