- Block dangerous operations before they execute, like destructive shell commands or unauthorized file access
- Log and audit every tool call for compliance, debugging, or analytics
- Transform inputs and outputs to sanitize data, inject credentials, or redirect file paths
- Require human approval for sensitive actions like database writes or API calls
- Track session lifecycle to manage state, clean up resources, or send notifications
How hooks work
1
An event fires
Something happens during agent execution and the SDK fires an event: a tool is about to be called (
PreToolUse), a tool returned a result (PostToolUse), a subagent started or stopped, the agent is idle, or execution finished. See the full list of events.2
The SDK collects registered hooks
The SDK checks for hooks registered for that event type. This includes callback hooks you pass in
options.hooks and shell command hooks from settings files when the corresponding settingSources or setting_sources entry is enabled, which it is for default query() options.3
Matchers filter which hooks run
If a hook has a
matcher pattern (like "Write|Edit"), the SDK tests it against the event’s target (for example, the tool name). Hooks without a matcher run for every event of that type.4
Callback functions execute
Each matching hook’s callback function receives input about what’s happening: the tool name, its arguments, the session ID, and other event-specific details.
5
Your callback returns a decision
After performing any operations (logging, API calls, validation), your callback returns an output object that tells the agent what to do: allow the operation, block it, modify the input, or inject context into the conversation.
PreToolUse hook (step 1) with a "Write|Edit" matcher (step 3) so the callback only fires for file-writing tools. When triggered, the callback receives the tool’s input (step 4), checks if the file path targets a .env file, and returns permissionDecision: "deny" to block the operation (step 5):
.env file, the hook denies the tool call, and Claude’s final response explains that it can’t create .env files.
Available hooks
The SDK provides hooks for different stages of agent execution. Some hooks are available in both SDKs, while others are TypeScript-only.Configure hooks
To configure a hook, pass it in thehooks field of your agent options (ClaudeAgentOptions in Python, the options object in TypeScript). This snippet assumes you have already defined a hook callback, like protect_env_files in Python or protectEnvFiles in TypeScript from the example above:
hooks option is a dictionary in Python or an object in TypeScript, where:
- Keys: hook event names such as
'PreToolUse','PostToolUse', and'Stop' - Values: arrays of matchers, each containing an optional filter pattern and your callback functions
Matchers
Use matchers to filter when your callbacks fire. Thematcher field matches against a different value depending on the hook event type. For example, tool-based hooks match against the tool name, while Notification hooks match against the notification type.
SDK matchers follow the same rules as matchers in settings files. That section documents the exact-string and regular-expression evaluation paths, their version requirements, and the matcher values for each event type.
Use the
matcher pattern to target specific tools whenever possible. A matcher with 'Bash' only runs for Bash commands, while omitting the pattern runs your callbacks for every occurrence of the event. Omit it on purpose to log every tool call your session makes.
Callback functions
Inputs
Every hook callback receives three arguments:- Input data: a typed object containing event details. Each hook type has its own input shape. For example,
PreToolUseHookInputincludestool_nameandtool_input, whileNotificationHookInputincludesmessage. See the full type definitions in the TypeScript and Python SDK references.- All hook inputs share
session_id,cwd, andhook_event_name. agent_idandagent_typeare populated when the hook fires inside a subagent. In TypeScript, these are on the base hook input and available to all hook types. In Python, they are optional fields onPreToolUse,PostToolUse,PostToolUseFailure, andPermissionRequest, and required fields onSubagentStartandSubagentStop.
- All hook inputs share
- Tool use ID (
str | None/string | undefined): correlatesPreToolUseandPostToolUseevents for the same tool call. - Context: in TypeScript, contains a
signalproperty (AbortSignal) for cancellation. In Python, this argument is reserved for future use.
Outputs
Your callback returns an object with two categories of fields:- Top-level fields are accepted on every event:
systemMessageshows a message to the user, andcontinue(continue_in Python) determines whether the agent keeps running after this hook. Some events discard them or deliver them elsewhere. Each event’s section on the hooks page says where they land. hookSpecificOutputcontrols the current operation. The fields you set inside depend on the hook event type:- For
PreToolUsehooks, this is where you setpermissionDecision("allow","deny","ask", or"defer"),permissionDecisionReason, andupdatedInput. If you return"defer", the query ends so you can resume it later. - For
PostToolUsehooks, you can setadditionalContextto append information to the tool result. To replace the tool’s output before Claude sees it, setupdatedToolOutput, which works for any tool in both SDKs. The olderupdatedMCPToolOutputfield replaces MCP tool output only and is deprecated. - In the TypeScript SDK, a
PostToolUsecallback can also returnclassifierContext, a short note about the tool call’s result for the auto mode permission classifier. Because your callback runs in your application’s own process, the classifier may weigh a user statement you relay in the note as user intent. The field requires TypeScript Agent SDK v0.3.236 or later. Annotate a result for the auto mode classifier covers the length cap, the synchronous-only rule, and what not to put in the note.
- For
{} to allow the operation without changes. SDK callback hooks use the same JSON output format as Claude Code shell command hooks, which documents every field and event-specific option. For the SDK type definitions, see the TypeScript and Python SDK references.
When multiple hooks or permission rules apply,
deny takes priority over defer, which takes priority over ask, which takes priority over allow. If any hook returns deny, the operation is blocked regardless of other hooks.Asynchronous output
By default, the agent waits for your hook to return before proceeding. If your hook performs a side effect, such as logging or sending a webhook, and doesn’t need to influence the agent’s behavior, you can return an async output instead. This tells the agent to continue immediately without waiting for the hook to finish. In this snippet,send_to_logging_service in Python and sendToLoggingService in TypeScript stand in for any logging function you define:
Async outputs can’t block, modify, or inject context into the operation since the agent has already moved on. Use them only for side effects like logging, metrics, or notifications.
Examples
Several examples in this section show only the callback function. To run one, register the callback under the matching event in thehooks field of your options, as shown in Configure hooks.
Modify tool input
This example intercepts Write tool calls and rewrites thefile_path argument to prepend /sandbox, redirecting all file writes to a sandboxed directory. The callback returns updatedInput with the modified path and permissionDecision: 'allow' to auto-approve the rewritten operation:
Pair
updatedInput with permissionDecision: 'allow' to auto-approve the modified input, or permissionDecision: 'ask' to show it to the user. If you omit permissionDecision, the modified input still applies and flows through the normal permission evaluation. With 'defer', updatedInput is ignored. Always return a new object rather than mutating the original tool_input../sandbox or /tmp/sandbox (macOS doesn’t allow creating a root-level /sandbox directory), then ask the agent to write a file: the Write tool’s result in the message stream names the path with your sandbox prefix rather than the one Claude requested.
Add context and block a tool
This example blocks writes to the/etc directory and explains why to both the model and the user:
permissionDecision: 'deny'stops the tool call.permissionDecisionReasontells the model why, so it avoids retrying.systemMessageshows the user what happened.
Auto-approve specific tools
By default, the agent may prompt for permission before using certain tools. This example auto-approves read-only filesystem tools (Read, Glob, Grep) by returningpermissionDecision: 'allow', letting them run without user confirmation while leaving all other tools subject to normal permission checks:
Register multiple hooks
When an event fires, all matching hooks run in parallel. For permission decisions, the most restrictive result applies: a singledeny blocks the tool call regardless of what the other hooks return. Because completion order is non-deterministic, write each hook to act independently rather than relying on another hook having run first.
The example below registers three independent checks for every tool call:
Filter with multi-tool matchers
Use multi-tool matchers to share one callback across related tools. This example registers three matchers with different scopes:- A pipe-separated exact list (
Write|Edit|NotebookEdit) triggersfile_security_hookonly for file modification tools. - A regex (
^mcp__) triggersmcp_audit_hookfor any MCP tool whose name starts withmcp__. - An omitted matcher triggers
global_loggerfor every tool call regardless of name.
Track subagent activity
UseSubagentStop hooks to monitor when subagents finish their work. See the full input type in the