The Interception Point

A close read of PreToolUse and PostToolUse — one controls a call before execution; the other can annotate or transform a successful result before Claude continues.

Pre  gates the call Post  shapes the result 13 sections
01Position

Where They Sit

PreToolUse runs before execution. A successful call reaches PostToolUse; a failed call reaches PostToolUseFailure.

Claude selects a tool PreToolUse allow · deny · ask · defer or replace tool input Tool executes Bash · Edit · MCP… PostToolUse successful result annotate or replace for Claude PostToolUseFailure failed result observe or guide recovery success failure
02Input shape

A Shared Base, Specific Payloads

Both hooks receive common session and tool-call metadata. PostToolUse additionally receives the successful tool_response.

ScopeFields and meaning
sharedtool_name, tool_input and tool_use_id identify and correlate the call
sharedsession_id, transcript_path, cwd and hook_event_name locate the event
sharedpermission_mode: default, auto, acceptEdits, dontAsk, bypassPermissions or plan
conditionalagent_id / agent_type identify subagent calls; agent type may also identify a configured session agent
Post onlytool_response contains the successful tool’s structured output
03PreToolUse

The Gate

A PreToolUse hook answers one question — should this call happen, and with what input? — via hookSpecificOutput.permissionDecision.

Hook runs permissionDecision allow ask defer deny runs, no prompt forces a user prompt pause non-interactive run; resume later blocked — reason sent to Claude
updatedInput replaces the full input object. Pair it with allow to auto-approve, ask to request confirmation, or omit the decision to continue through normal permission evaluation. With defer, it is ignored.
04PostToolUse

The Rewrite

The tool succeeded and already ran — PostToolUse cannot undo it. Its principal role is to shape the result and context Claude receives next.

Raw result tool_response PostToolUse hook additionalContext updatedToolOutput What Claude reads as the tool result
updatedToolOutput replaces the result for any tool, MCP or built‑in. updatedMCPToolOutput is the deprecated, MCP‑only predecessor. additionalContext appends rather than replaces — use it to annotate without discarding the original.
05Exit codes

The Same Code, Two Meanings

A hook can answer purely through its exit code — but the same code means something different depending on which side of the call it runs on.

Exit codePreToolUsePostToolUse
0Success — JSON on stdout is read, no blockSuccess — JSON on stdout is read
2Blocks the call — stderr becomes the reason Claude seesCannot block — the tool already ran; stderr is sent to Claude as feedback
otherNon‑blocking error — call proceedsNon‑blocking error — nothing to stop
When stdout contains a valid JSON object that passes the hook-output schema, JSON alone decides the outcome for standard decision events. On PreToolUse, permissionDecision: "allow" can therefore override exit 2.
06Matchers

Scoping a Hook

The matcher string decides which tool calls reach the hook at all — and it's read two different ways depending on what's in it.

exact / list

Plain names

Only letters, digits, _ -, spaces, commas, pipes → matched literally.

"Bash" · "Edit|Write" · "Edit, Write"

regex

Anything else

A paren, bracket, *, ., ^ or $ → the whole string becomes a regex.

"mcp__memory__.*" · "^mcp__" · ".*"

An empty, omitted or * matcher catches every tool. Matchers inspect the tool name, not its arguments; use handler logic or an if rule to narrow by command, path or operation.
07settings.json

Config Anatomy

Inside the top-level hooks object, three conceptual levels connect an event to the handlers that run.

hooks top level PreToolUse / PostToolUse matcher group { matcher, hooks: […] } handler type: command… command timeout container → event → matcher group → one or more handlers Pre/Post support command · http · mcp_tool · prompt · agent; default 600s
08Two ways to define one

Config File or Callback

The Agent SDK can replace an external hook command with an in-process callback. It can also load settings-based hooks when configured to do so.

settings.json
// wrapper reads tool_input.file_path
{
  "hooks": {
    "PostToolUse": [{
      "matcher": "Edit|Write",
      "hooks": [{
        "type": "command",
        "command":
          "${CLAUDE_PROJECT_DIR}/.claude/hooks/format-file.sh",
        "args": []
      }]
    }]
  }
}
Agent SDK (Python)
from claude_agent_sdk import (
  ClaudeAgentOptions, HookMatcher
)

options = ClaudeAgentOptions(
  hooks={
    "PreToolUse": [
      HookMatcher(
        matcher="Bash",
        hooks=[my_guard]  # your fn
      )
    ]
  }
)
09Concurrency

When Hooks Collide

Every hook matching an event fires in parallel, not in the order they're declared.

hook A → allow hook B → ask hook C → deny deny wins precedence: deny > defer > ask > allow independent hooks; no ordering contract
Conflicting mutations are unsafe. If several checks may rewrite the same input or output, consolidate the transformation into one hook and keep the other hooks observational.
10Boundaries

Security & Limits

What hooks can enforce — and what they can't get around.

runs as you

Full user permissions

A hook script has your filesystem access and credentials — it validates its own input, nothing sandboxes it for you.

deny always wins

Even in bypassPermissions

A PreToolUse deny blocks the call in every permission mode. allow can't override a settings‑level deny rule.

can't undo

PostToolUse is too late

By the time it runs, side effects already happened — it can only shape what Claude reads about them.

failure semantics

Timeouts differ by event

These tool hooks default to 600s. A timed-out PreToolUse callback prevents that call and returns a timeout result to Claude.

11In the wild

Four Patterns

Most real hooks are one of these, wearing different clothes.

PreToolUse

Secrets gate

Scan tool_input for a Bash command touching .env or a key pattern → deny with a reason.

PostToolUse

Auto‑format

Matcher Edit|Write runs a wrapper that reads tool_input.file_path, then formats the touched file.

PostToolUse

Output normalization

One hook sets updatedToolOutput so Claude receives one normalized result shape across MCP and built‑in tools.

Lifecycle

Durable audit trail

Log attempts in PreToolUse and outcomes in PostToolUse / PostToolUseFailure. Store records outside conversation context.

12The model

Two Questions, One Boundary

PreToolUse decides whether it happens PostToolUse shapes what Claude receives after success

Before the call, you guard the gate. After success, you shape the result. Failure has its own recovery hook.

01 / 13
background
use ← → or click the edges to navigate