Documentation contradiction: multi-hook firing order is documented as "parallel" in Agent SDK docs but "sequential with short-circuit" in hooks-guide
The Claude Code documentation contradicts itself on what happens when multiple hooks are registered on the same matcher (or fire on the same event). Two official docs say different things.
Source 1 — Agent SDK hooks reference (https://code.claude.com/docs/en/agent-sdk/hooks)
"When an event fires, all matching hooks run in parallel. For permission decisions, the most restrictive result wins: a single deny blocks the tool call regardless of what the other hooks return."
Source 2 — Hooks guide (https://code.claude.com/docs/en/hooks-guide)
"Hooks run in order, and the first failure blocks the rest. If one hook exits with a blocking exit code (exit code 2), subsequent hooks won't run."
Why this is a contradiction
These describe different mechanics:
- "Parallel + restrictive-result-wins" implies all hooks always run; their results are merged after every hook completes.
- "Sequential + short-circuit on exit code 2" implies hooks run in a deterministic order and a blocking hook prevents subsequent hooks from running at all.
A hook author cannot predict which behavior applies.
Why it matters
Concrete scenario: two hooks register on PreToolUse(ExitPlanMode):
- A model-tier guardrail that exits 2 (or returns
decision: "block") when the active model is wrong. - A side-effect hook that writes the approved plan to a file and updates a tracker issue.
If "parallel" is correct, the file-write happens regardless of the guardrail's verdict — bad outcome (we wrote a wrong-tier plan).
If "sequential with short-circuit on exit code 2" is correct, registration order matters and the file-write is correctly suppressed when the guardrail blocks — good outcome, but only if the project knows to register the guardrail first.
Without a definitive answer, hook authors either over-engineer (re-implement the guardrail check inside every dependent hook) or under-engineer (assume sequential and get burned when the runtime is parallel).
Asks
- Which behavior is the actual implementation in current Claude Code?
- If parallel: how should the "blocking exit code 2 stops subsequent hooks" passage in the hooks-guide be reconciled?
- If sequential: does the short-circuit apply only to exit-code-based blocking, or also to JSON
decision: "block"? And what determines firing order — registration order insettings.json, alphabetical, or something else? - Once resolved, please update both docs to match.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗