One schema-invalid hook matcher silently disables ALL settings.json hooks (no warning); PostToolUse never fires for MCP tools
Summary
Two related hook-dispatch findings, both verified by controlled A/B on the same machine:
- One schema-invalid matcher entry silently disables ALL
settings.jsonhooks — every event type, every hook — with no error or warning surfaced anywhere. In our case this produced a 30-hour total outage of ~100 hooks (telemetry, guardrails, enforcement) that looked identical to "nothing to report." - Tolerance for the invalid entry changed across an automatic CLI update. The same config had worked for weeks on 2.1.19x; after auto-update (verified failing on 2.1.202) every newly started session loaded zero hooks. Sessions started before the update kept firing until they ended, which made the regression look like a mysterious time-based outage rather than a config issue.
Separately (item 3, pre-dating the outage): PostToolUse hooks never fire for MCP tools (mcp__<server>__<tool>), verified over a 3-week window during which hook dispatch was otherwise healthy.
Environment
- Claude Code 2.1.197 → 2.1.202 (macOS, Darwin 25.5.0)
- Reproduced on entrypoints:
claude-desktop,cli
Repro 1 — silent whole-config rejection (the severe one)
settings.json:
{
"hooks": {
"UserPromptSubmit": [
{"hooks": [{"type": "command", "command": "touch /tmp/hook-fired"}]}
],
"Stop": [
{"matcher": {"type": "always"}, "hooks": [{"type": "command", "command": "/usr/bin/true"}]}
]
}
}
- With the object-valued
matcherpresent:/tmp/hook-firedis never created — the valid UserPromptSubmit hook is dead too. No stderr, no doctor warning, nothing. - Remove the
"matcher": {"type": "always"}entry (or make it a string): the UserPromptSubmit hook fires normally. - Note: an invalid-regex string matcher (
"matcher": "*") is tolerated — only the non-string type kills the config.
Expected: per-entry validation — skip the malformed entry with a visible warning and run the rest — or at minimum surface the whole-config rejection loudly. Users running enforcement/safety hooks currently lose all of them silently.
Repro 2 — PostToolUse never fires for MCP tools
- Add a PostToolUse hook with matcher
mcp__.*(or"") whose command appends to a marker file. - In an interactive session, call any MCP tool; confirm the tool ran.
- Marker is never written for MCP tools; built-in tools (Read/Bash) do write it.
Observed consistently for 3 weeks (an always-recording telemetry wrapper on the hook logged 0 invocations across thousands of MCP calls, while Stop/UserPromptSubmit hooks logged thousands).
Impact
- Repro 1 turns a config typo into a silent global kill-switch for the entire hooks layer — the failure mode is indistinguishable from "all checks pass."
- Repro 2 makes MCP tool calls invisible to PostToolUse-based telemetry, validation, and guardrails.
Workaround (for anyone else hitting this)
Validate hook matchers yourself (every matcher must be a string; regexes must compile), and alarm on hook-telemetry silence — we now run both checks continuously after losing 30 hours to this.