Skip to content

Hook Exit Codes And JSON Output

Definition

How a shell hook communicates back to Claude Code: via its process exit code (success / block / error) and, on success, structured JSON on stdout that can allow, deny, or rewrite an action.

Key points

  • Exit 0 = success: stdout parsed as JSON. For most events stdout goes to the debug log only; exceptions UserPromptSubmit, UserPromptExpansion, SessionStart feed stdout to Claude as context.
  • Exit 2 = blocking: stdout/JSON ignored, stderr is fed to Claude. Effect per event: PreToolUse blocks the call; UserPromptSubmit rejects the prompt; PermissionRequest denies; Stop prevents stopping; WorktreeCreate non-zero fails creation.
  • Other exit (non-0/non-2) = non-blocking error: transcript shows <hook> hook error + first stderr line; execution continues.
  • JSON fields (universal): continue (default true; Python continue_), stopReason, suppressOutput, systemMessage (shown to user, not model), terminalSequence. Decision: top-level decision: "block" + reason.
  • hookSpecificOutput (requires hookEventName): PreToolUse → permissionDecision (allow/deny/ask/defer), permissionDecisionReason, updatedInput (needs allow/ask), additionalContext; PostToolUse → updatedToolOutput, additionalContext; SessionStart → additionalContext, initialUserMessage, sessionTitle, watchPaths, reloadSkills; PermissionRequest → decision:{behavior,updatedInput}; PermissionDenied → retry:true.
  • Multi-hook priority: deny > defer > ask > allow — any single deny blocks.
  • Async: {async:true, asyncTimeout:30000} (Python async_); asyncRewake:true wakes Claude on exit 2.

Why it matters for the exam

  • D3 (20% of scored content) leans hard on the exit 0 / exit 2 / other semantics and where stdout vs stderr goes — a common multiple-choice discriminator.
  • PreToolUse returning deny (or exit 2) is the reliable enforcement answer — see PreToolUse hook enforcement.

Common gotchas

  • On exit 2, JSON on stdout is ignored — only stderr reaches Claude.
  • updatedInput must live inside hookSpecificOutput alongside permissionDecision: "allow" (or ask), not at the top level.
  • Multi-hook resolution is deny-wins (deny > defer > ask > allow), mirroring deny-beats-allow.

See also

Sources