claude/for
Search
Subscribe
Claude CodeUpdated

How do I make Claude Code hooks fail closed with `onFailure: "block"`?

Claude Code 2.1.295 adds onFailure: "block" for command and HTTP hooks: a hook that can't start, times out, or exits oddly blocks the action. Docs lag.

From Claude Code 2.1.295, command and HTTP hooks accept onFailure: "block". With it, a hook that can't start, times out, or exits with an unexpected code blocks the action instead of letting it through. That wording comes from the CHANGELOG. As of 2026-10-09 ~08:40 CEST, the official hooks reference does not mention onFailure yet, so treat placement details below as inferred.

Why you need it: hooks fail open by default

The hooks reference is explicit about the default (checked 2026-10-09):

  • For most events, exit code 2 is the only exit code that blocks on its own. Exit code 1 without valid JSON is a non-blocking error and the action proceeds.
  • A hook that can't start (missing or non-executable script, shell exit 127) lands in the same non-blocking bucket. The docs warn that "a mistyped path in settings.json leaves the gate silently disabled."
  • On PreToolUse, a timed-out command hook lets the tool call continue. Default timeout for command and http hooks is 600 seconds on most events.

So a policy hook that crashes, hangs, or was never installed correctly lets everything through. onFailure: "block" flips that for command and HTTP hooks.

What 2.1.295 added

Added onFailure: "block" for command and HTTP hooks: a hook that can't start, times out, or exits with an unexpected code blocks the action instead of letting it through

Scope from that bullet: type: "command" and type: "http" only. Nothing is said about prompt, agent, or mcp_tool hooks, so don't assume it works there.

How to set it

onFailure is a per-hook field, next to type and timeout. [Inferred] placement, since docs don't show it yet:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/guard-bash.sh",
            "timeout": 30,
            "onFailure": "block"
          }
        ]
      }
    ]
  }
}

Then:

  1. Upgrade to ≥ 2.1.295 (claude --version).
  2. Keep your intentional block on exit 2. onFailure covers the unexpected failures; it doesn't replace your policy logic.
  3. Set a short, explicit timeout. With fail-closed, a hung hook now blocks, so 600 s of waiting is a long stall.
  4. Test the failure path: point command at a path that doesn't exist and confirm the tool call is blocked, not run with a non-blocking notice.

For org-wide enforcement, ship the hook from managed settings so a repo can't drop it.

Docs lag: what to trust

ClaimSourceStatus 2026-10-09
onFailure: "block" exists, command + HTTPCHANGELOG 2.1.295Verified
Default is fail-open (exit 1, can't start, PreToolUse timeout)Hooks referenceVerified
Field sits on each hook entryOur inference[Inferred], recheck when docs update
Other onFailure values—[GAP], not documented
Behavior on non-blockable events (e.g. SessionStart)—[GAP]

Pitfalls

  • Exit 1 as policy. Still non-blocking unless the hook fails in a way onFailure catches. Use exit 2 for "deny."
  • Long timeouts + fail-closed. Every slow run becomes a block after the full wait.
  • Async hooks. The docs say Claude Code doesn't enforce timeout on async: true command hooks; don't count on fail-closed for those.
  • Mods. 2.1.295 also fixed a mod hook receiving deeply nested tool input cut short with no error. Upgrade if you guard with mods.

FAQ

Does onFailure: "block" change what exit 2 does? No. Exit 2 already blocks on blockable events. onFailure covers can't-start, timeout, and unexpected exit codes.

Which version do I need? 2.1.295 or later. See our 2.1.295 notes.

Is this related to retry settings? No. Retry waits on 429/529 are separate; see the overloaded retry Spec.

Sources (checked 2026-10-09 ~08:40 CEST)

  • Claude Code CHANGELOG.md: 2.1.295 onFailure bullet and mod-hook fix. The CHANGELOG doesn't date versions, so the release day is [GAP].
  • Hooks reference: exit-code behavior, can't-start bucket, timeouts and defaults, async hooks. No onFailure entry on this check.

One Claude move in your inbox, every Sunday

Four minutes, tested on a real job, then back to your weekend. Free.