PreToolUse checks a tool call before it runs. PostToolUse adds context or changes the result Claude reads after success.
PreToolUse runs before execution. Once a tool starts, success reaches PostToolUse and an execution error reaches PostToolUseFailure.
Both hooks receive common session and tool-call metadata. PostToolUse additionally receives the successful tool_response.
| Scope | Fields and meaning |
|---|---|
| shared | tool_name, tool_input and tool_use_id identify and correlate the call |
| shared | session_id, transcript_path, cwd and hook_event_name locate the event |
| shared | permission_mode: default, auto, acceptEdits, dontAsk, bypassPermissions or plan |
| conditional | agent_id / agent_type identify subagent calls; agent type may also identify a configured session agent |
| Post only | tool_response contains the successful tool’s structured output |
Should this tool run, and with what input? Command, HTTP, MCP and SDK hooks answer through hookSpecificOutput.permissionDecision.
The tool has already run. A hook can add a note, rewrite what Claude reads, or do follow-up work such as formatting a file. It does not roll back the original action.
An exit code tells Claude Code how the hook finished. JSON can carry a decision too. For PreToolUse, exit 2 always blocks the call.
| Exit code | PreToolUse | PostToolUse |
|---|---|---|
| 0 | Read valid JSON. A JSON deny still blocks. | Apply valid JSON notes or output changes. |
| 2 | Block, even if JSON says allow. stderr explains why. | Send stderr as feedback. The tool has already run. |
| other | Usually no decision. Valid JSON can still apply.* | Usually an error notice. Valid JSON can still apply.* |
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.
Only letters, digits, _ -, spaces, commas, pipes → matched literally.
"Bash" · "Edit|Write" · "Edit, Write"
A paren, bracket, *, ., ^ or $ → the whole string becomes a regex.
"mcp__memory__.*" · "^mcp__" · ".*"
Inside the top-level hooks object, three conceptual levels connect an event to the handlers that run.
The event says when to run. The handler type says what runs and how it replies. These defaults apply to PreToolUse and PostToolUse.
| Handler | How it replies | Default wait |
|---|---|---|
| command / http / mcp_tool | Your code returns hook JSON. Command hooks also use exit codes. | 600 seconds |
| prompt | A model returns { "ok": true }, or false with a reason. | 30 seconds |
| agent | An agent checks files or tools, then returns ok and a reason. Experimental. | 60 seconds |
| SDK callback | Your application function returns hook JSON directly. | 600 seconds |
A settings hook starts a program. An SDK callback runs in your application. Here, both respond after a file edit. Define the formatter before using either example.
{
"hooks": {
"PostToolUse": [{
"matcher": "Edit|Write",
"hooks": [{
"type": "command",
"command": "node",
"args": [
"${CLAUDE_PROJECT_DIR}/format.mjs"]
}]
}]
}
}
from claude_agent_sdk import ( ClaudeAgentOptions, HookMatcher ) options = ClaudeAgentOptions( hooks={ "PostToolUse": [HookMatcher( matcher="Edit|Write", hooks=[format_file] # async callback )] } )
Matching hooks can run in parallel. Their declaration order does not control their execution order. Prompt and agent hooks have concurrency limits, so some may wait.
What hooks can enforce — and what they can't get around.
A command hook runs with your operating-system access, outside the tool sandbox. Validate its input and give it only the credentials it needs.
A PreToolUse deny blocks the call in every permission mode. allow can't override a settings‑level deny rule.
Changing a result does not reverse a file write or network request. Earlier telemetry may also contain the original output.
A timed-out PreToolUse callback blocks that call. A timed-out PostToolUse callback keeps the tool result. Claude can continue in both cases.
Choose the hook based on what you need to prevent, change or record.
Inspect tool_input and deny a forbidden file access. A Bash text check is only one guard: other tools or indirect commands need protection too.
Matcher Edit|Write runs a wrapper that reads tool_input.file_path, then formats the touched file.
Rewrite a result with updatedToolOutput while keeping that tool’s schema. Add a common summary with additionalContext.
Record attempts in PreToolUse and execution outcomes in the Post hooks. Record denials in your guard too. Early validation failures need separate logging.
Before execution, check whether the call should run. After success, add context or adjust its result. Use PostToolUseFailure for execution errors.
Reviewed 27 October 2026 against Claude Code 2.1.312 release notes and current Anthropic documentation. Older installations may behave differently.
| Read more | What it explains |
|---|---|
| Hooks reference | Events, permissions, exit codes and output schemas |
| Hooks guide | Practical examples, handler types and timeout defaults |
| Agent SDK hooks / Python API | Callback setup, return values and timeout behavior |
| Claude Code changelog | onFailure: "block" requires 2.1.295+. Prompt and agent concurrency limits can change. |
| SDK compatibility note | updatedMCPToolOutput is deprecated. Use updatedToolOutput. |