Claude Code hooks fire unreliably — no single hook is a security enforcement point
Claude Code hooks fire unreliably — no single hook is a security enforcement point
From Theory Delta | Methodology | Published 2026-03-03
What you expect
You configure PreToolUse and PostToolUse hooks to enforce policies — blocking dangerous commands, running tests, or logging tool calls. The documentation presents hooks as the primary mechanism for customizing and securing Claude Code’s behavior. Your security policy depends on hooks firing before certain tool calls happen.
What actually happens
Hooks fail in 25+ confirmed ways across five categories. No single hook event is a reliable enforcement point.
1. Silent non-firing. PreToolUse and PostToolUse hooks intermittently fail to trigger entirely. The hook is configured, the tool runs, the hook does not execute. No error, no log entry — the operation proceeds as if the hook does not exist (Issue #6305, open, 21 comments, has repro). This affects all matchers: "*", "Bash", "Task", "Write", "Edit".
2. Ignored decisions. PermissionRequest hook decisions are completely ignored (Issue #19298, closed not-planned). The hook executes, returns correct JSON output with "permissionDecision": "deny", and the interactive permission prompt appears anyway. The hook’s decision is not honored. PermissionRequest hooks are currently non-functional for decision control.
3. Platform breakage. Windows has 5+ distinct hook failure modes absent on macOS or Linux: PowerShell spawn causes complete input freeze, socket buffer exhaustion on Edit|Write hooks, Desktop App hooks that match in logs but never execute, and PostToolUse HTTP hooks that silently send no requests on Windows (Issue #32977). A hook that works reliably in your development environment may fail silently in your Windows CI.
4. Data corruption. When multiple PreToolUse hooks match the same tool, updatedInput from earlier hooks is silently overwritten by the last hook’s result — even if that hook returned no updatedInput (Issue #15897, open, has repro). Any multi-hook setup that modifies tool inputs is silently broken. Agent hook prompt fields are also silently stripped from settings.json during sessions, causing schema validation errors that disable all hooks on the next startup (Issue #24920).
5. Architectural constraints. Plugin hooks stop firing entirely after context compaction (Issue #25655, closed not-planned). 347 hook events before compaction, then zero — confirmed with transcript analysis. Background Task() subagents bypass Stop hooks entirely (Issue #25147, closed not-planned). PostToolUse hooks do not fire on Write tool invocations — only Edit triggers them, leaving Write-generated files entirely outside PostToolUse enforcement. PreToolUse exit code 2 does NOT block Task tool calls — 100% failure rate across 19 Task calls tested in v2.2.19.
The per-call overhead is also non-trivial. Sessions with multiple blocking hooks add 375–525ms of blocking latency per Bash tool call due to process spawn overhead.
What this means for you
If you rely on a single PreToolUse hook to block dangerous commands: that hook has a documented, confirmed probability of not firing on any given call. Your security boundary is not a boundary — it is a best-effort gate with known, unfixed silent failure modes.
If you use hooks on a Windows deployment: you have 5 additional failure modes that your macOS development environment will never surface. Platform-specific failures are common and do not appear in the main docs.
If you run background Task() subagents: your Stop hooks do not run for their completions. Any quality gate — tests, linting, policy checks — attached to Stop hooks is silently bypassed for all background agent work.
If your hooks fire after context compaction: they don’t. After compaction, plugin hooks stop executing for the remainder of the session. Long sessions silently lose their hook protection without any indication that this happened.
If you use PostToolUse for auto-testing or auto-linting: Write tool invocations are invisible to PostToolUse. Agents that use Write instead of Edit bypass all PostToolUse enforcement. This is a tool-shaped blind spot, not a configuration error.
What to do
-
Never rely on a single hook for security enforcement. Use
PreToolUseANDPostToolUseAND external monitoring. Any individual hook has a non-zero documented probability of not firing. -
Add the
PostCompacthook to re-inject critical configuration. This is the structural mitigation for the compaction-loss problem. ThePostCompacthook itself is subject to the same non-firing issues, so it is a mitigation, not a guarantee. -
Test hooks on your deployment platform. If you develop on macOS and deploy on Windows, test hooks in the Windows environment. Platform-specific failures are confirmed and documented.
-
Monitor hook execution independently. Have hooks log to an external file or service. If a log entry is missing, the hook did not fire. Without external logging, silent non-firing is invisible.
-
Keep hooks simple. Hooks that modify arguments are more likely to produce data corruption (the
updatedInputmulti-hook overwrite). Prefer hooks that block or allow without modifying the payload. If you must modify arguments, consolidate all modification logic into a single hook. -
Treat
PreToolUseas covering Bash only for blocking. Exit code 2 reliably blocks Bash. It does not reliably block Task tool calls (0% success rate at v2.2.19). Hook-based enforcement has a Task-shaped blind spot that combines with the background agent Stop hook bypass.
Evidence
| Failure | Issue | Status |
|---|---|---|
| PreToolUse/PostToolUse intermittent non-firing (all matchers) | #6305 | Open, has repro, 21 comments |
| PermissionRequest hook decision completely ignored | #19298 | Closed not-planned |
| Background Task() agents bypass Stop hooks | #25147 | Closed not-planned |
| Plugin hooks stop firing after context compaction | #25655 | Closed not-planned |
| updatedInput silently dropped with multiple PreToolUse hooks | #15897 | Open, has repro |
| Agent hook prompt fields silently stripped (causes full hook disable on restart) | #24920 | Closed not-planned |
| PostToolUse HTTP hooks silently not dispatched on Windows v2.1.72 | #32977 | Closed as duplicate |
| PreToolUse exit code 2 does not block Task tool calls (v2.2.19, 19/19 failures) | Empirical (Theory Delta, v2.2.19) | Confirmed |
| PostToolUse does not fire on Write tool invocations — only Edit | Empirical (Theory Delta, PR #1046 agent runs) | Confirmed |
Confidence: empirical — failure modes confirmed through runtime testing across multiple sessions and platforms. Silent non-firing and ignored decisions observed directly. Windows-specific failures documented via issue reports. PostToolUse Write gap and Task blocking gap validated empirically.
Falsification criterion: This claim would be disproved by demonstrating that PreToolUse hooks fire with 100% reliability across 1,000+ tool calls in a single session, including after context compaction events and Task tool calls, on all supported platforms.
Seen different? Contribute your evidence — theory delta is what makes this knowledge base work.