One schema-invalid hook matcher silently disables ALL settings.json hooks (no warning); PostToolUse never fires for MCP tools

Open 💬 0 comments Opened Jul 7, 2026 by thomasdigital

Summary

Two related hook-dispatch findings, both verified by controlled A/B on the same machine:

  1. One schema-invalid matcher entry silently disables ALL settings.json hooks — 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."
  2. 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 matcher present: /tmp/hook-fired is 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

  1. Add a PostToolUse hook with matcher mcp__.* (or "") whose command appends to a marker file.
  2. In an interactive session, call any MCP tool; confirm the tool ran.
  3. 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.

View original on GitHub ↗