1> ## Documentation Index
2> Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt
3> Use this file to discover all available pages before exploring further.
4
1# Hooks reference5# Hooks reference
2 6
3> This page provides reference documentation for implementing hooks in Claude Code.7> Reference for Claude Code hook events, configuration schema, JSON input/output formats, exit codes, async hooks, HTTP hooks, prompt hooks, and MCP tool hooks.
4 8
5<Tip>9<Tip>
6 For a quickstart guide with examples, see [Get started with Claude Code hooks](/en/hooks-guide).10 For a quickstart guide with examples, see [Automate workflows with hooks](/en/hooks-guide).
7</Tip>11</Tip>
8 12
9## Configuration13Hooks are user-defined shell commands, HTTP endpoints, or LLM prompts that execute automatically at specific points in Claude Code's lifecycle. Use this reference to look up event schemas, configuration options, JSON input/output formats, and advanced features like async hooks, HTTP hooks, and MCP tool hooks. If you're setting up hooks for the first time, start with the [guide](/en/hooks-guide) instead.
10 14
11Claude Code hooks are configured in your [settings files](/en/settings):15## Hook lifecycle
12 16
13* `~/.claude/settings.json` - User settings17Hooks fire at specific points during a Claude Code session. When an event fires and a matcher matches, Claude Code passes JSON context about the event to your hook handler. For command hooks, input arrives on stdin. For HTTP hooks, it arrives as the POST request body. Your handler can then inspect the input, take action, and optionally return a decision. Some events fire once per session, while others fire repeatedly inside the agentic loop:
14* `.claude/settings.json` - Project settings18
15* `.claude/settings.local.json` - Local project settings (not committed)19<div style={{maxWidth: "500px", margin: "0 auto"}}>
16* Managed policy settings20 <Frame>
17 21 <img src="https://mintcdn.com/claude-code/1wr0LPds6lVWZkQB/images/hooks-lifecycle.svg?fit=max&auto=format&n=1wr0LPds6lVWZkQB&q=85&s=53a826e7bb64c6bff5f867506c0530ad" alt="Hook lifecycle diagram showing the sequence of hooks from SessionStart through the agentic loop (PreToolUse, PermissionRequest, PostToolUse, SubagentStart/Stop, TaskCreated, TaskCompleted) to Stop or StopFailure, TeammateIdle, PreCompact, PostCompact, and SessionEnd, with Elicitation and ElicitationResult nested inside MCP tool execution and WorktreeCreate, WorktreeRemove, Notification, ConfigChange, InstructionsLoaded, CwdChanged, and FileChanged as standalone async events" width="520" height="1155" data-path="images/hooks-lifecycle.svg" />
18<Note>22 </Frame>
19 Enterprise administrators can use `allowManagedHooksOnly` to block user, project, and plugin hooks. See [Hook configuration](/en/settings#hook-configuration).23</div>
20</Note>24
21 25The table below summarizes when each event fires. The [Hook events](#hook-events) section documents the full input schema and decision control options for each one.
22### Structure26
23 27| Event | When it fires |
24Hooks are organized by matchers, where each matcher can have multiple hooks:28| :------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- |
29| `SessionStart` | When a session begins or resumes |
30| `UserPromptSubmit` | When you submit a prompt, before Claude processes it |
31| `PreToolUse` | Before a tool call executes. Can block it |
32| `PermissionRequest` | When a permission dialog appears |
33| `PostToolUse` | After a tool call succeeds |
34| `PostToolUseFailure` | After a tool call fails |
35| `Notification` | When Claude Code sends a notification |
36| `SubagentStart` | When a subagent is spawned |
37| `SubagentStop` | When a subagent finishes |
38| `TaskCreated` | When a task is being created via `TaskCreate` |
39| `TaskCompleted` | When a task is being marked as completed |
40| `Stop` | When Claude finishes responding |
41| `StopFailure` | When the turn ends due to an API error. Output and exit code are ignored |
42| `TeammateIdle` | When an [agent team](/en/agent-teams) teammate is about to go idle |
43| `InstructionsLoaded` | When a CLAUDE.md or `.claude/rules/*.md` file is loaded into context. Fires at session start and when files are lazily loaded during a session |
44| `ConfigChange` | When a configuration file changes during a session |
45| `CwdChanged` | When the working directory changes, for example when Claude executes a `cd` command. Useful for reactive environment management with tools like direnv |
46| `FileChanged` | When a watched file changes on disk. The `matcher` field specifies which filenames to watch |
47| `WorktreeCreate` | When a worktree is being created via `--worktree` or `isolation: "worktree"`. Replaces default git behavior |
48| `WorktreeRemove` | When a worktree is being removed, either at session exit or when a subagent finishes |
49| `PreCompact` | Before context compaction |
50| `PostCompact` | After context compaction completes |
51| `Elicitation` | When an MCP server requests user input during a tool call |
52| `ElicitationResult` | After a user responds to an MCP elicitation, before the response is sent back to the server |
53| `SessionEnd` | When a session terminates |
54
55### How a hook resolves
56
57To see how these pieces fit together, consider this `PreToolUse` hook that blocks destructive shell commands. The `matcher` narrows to Bash tool calls and the `if` condition narrows further to commands starting with `rm`, so `block-rm.sh` only spawns when both filters match:
25 58
26```json theme={null}59```json theme={null}
27{60{
28 "hooks": {61 "hooks": {
29 "EventName": [62 "PreToolUse": [
30 {63 {
31 "matcher": "ToolPattern",64 "matcher": "Bash",
32 "hooks": [65 "hooks": [
33 {66 {
34 "type": "command",67 "type": "command",
35 "command": "your-command-here"68 "if": "Bash(rm *)",
69 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm.sh"
36 }70 }
37 ]71 ]
38 }72 }
41}75}
42```76```
43 77
44* **matcher**: Pattern to match tool names, case-sensitive (only applicable for78The script reads the JSON input from stdin, extracts the command, and returns a `permissionDecision` of `"deny"` if it contains `rm -rf`:
45 `PreToolUse`, `PermissionRequest`, and `PostToolUse`)
46 * Simple strings match exactly: `Write` matches only the Write tool
47 * Supports regex: `Edit|Write` or `Notebook.*`
48 * Use `*` to match all tools. You can also use empty string (`""`) or leave
49 `matcher` blank.
50* **hooks**: Array of hooks to execute when the pattern matches
51 * `type`: Hook execution type - `"command"` for bash commands or `"prompt"` for LLM-based evaluation
52 * `command`: (For `type: "command"`) The bash command to execute (can use `$CLAUDE_PROJECT_DIR` environment variable)
53 * `prompt`: (For `type: "prompt"`) The prompt to send to the LLM for evaluation
54 * `timeout`: (Optional) How long a hook should run, in seconds, before canceling that specific hook
55 79
56For events like `UserPromptSubmit`, `Stop`, and `SubagentStop`80```bash theme={null}
57that don't use matchers, you can omit the matcher field:81#!/bin/bash
82# .claude/hooks/block-rm.sh
83COMMAND=$(jq -r '.tool_input.command')
84
85if echo "$COMMAND" | grep -q 'rm -rf'; then
86 jq -n '{
87 hookSpecificOutput: {
88 hookEventName: "PreToolUse",
89 permissionDecision: "deny",
90 permissionDecisionReason: "Destructive command blocked by hook"
91 }
92 }'
93else
94 exit 0 # allow the command
95fi
96```
58 97
59```json theme={null}98Now suppose Claude Code decides to run `Bash "rm -rf /tmp/build"`. Here's what happens:
60{99
61 "hooks": {100<Frame>
62 "UserPromptSubmit": [101 <img src="https://mintcdn.com/claude-code/-tYw1BD_DEqfyyOZ/images/hook-resolution.svg?fit=max&auto=format&n=-tYw1BD_DEqfyyOZ&q=85&s=c73ebc1eeda2037570427d7af1e0a891" alt="Hook resolution flow: PreToolUse event fires, matcher checks for Bash match, if condition checks for Bash(rm *) match, hook handler runs, result returns to Claude Code" width="930" height="290" data-path="images/hook-resolution.svg" />
63 {102</Frame>
64 "hooks": [103
104<Steps>
105 <Step title="Event fires">
106 The `PreToolUse` event fires. Claude Code sends the tool input as JSON on stdin to the hook:
107
108 ```json theme={null}
109 { "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... }
110 ```
111 </Step>
112
113 <Step title="Matcher checks">
114 The matcher `"Bash"` matches the tool name, so this hook group activates. If you omit the matcher or use `"*"`, the group activates on every occurrence of the event.
115 </Step>
116
117 <Step title="If condition checks">
118 The `if` condition `"Bash(rm *)"` matches because the command starts with `rm`, so this handler spawns. If the command had been `npm test`, the `if` check would fail and `block-rm.sh` would never run, avoiding the process spawn overhead. The `if` field is optional; without it, every handler in the matched group runs.
119 </Step>
120
121 <Step title="Hook handler runs">
122 The script inspects the full command and finds `rm -rf`, so it prints a decision to stdout:
123
124 ```json theme={null}
65 {125 {
66 "type": "command",126 "hookSpecificOutput": {
67 "command": "/path/to/prompt-validator.py"127 "hookEventName": "PreToolUse",
68 }128 "permissionDecision": "deny",
69 ]129 "permissionDecisionReason": "Destructive command blocked by hook"
70 }130 }
71 ]
72 }131 }
73}132 ```
74```133
134 If the command had been a safer `rm` variant like `rm file.txt`, the script would hit `exit 0` instead, which tells Claude Code to allow the tool call with no further action.
135 </Step>
136
137 <Step title="Claude Code acts on the result">
138 Claude Code reads the JSON decision, blocks the tool call, and shows Claude the reason.
139 </Step>
140</Steps>
141
142The [Configuration](#configuration) section below documents the full schema, and each [hook event](#hook-events) section documents what input your command receives and what output it can return.
143
144## Configuration
145
146Hooks are defined in JSON settings files. The configuration has three levels of nesting:
147
1481. Choose a [hook event](#hook-events) to respond to, like `PreToolUse` or `Stop`
1492. Add a [matcher group](#matcher-patterns) to filter when it fires, like "only for the Bash tool"
1503. Define one or more [hook handlers](#hook-handler-fields) to run when matched
151
152See [How a hook resolves](#how-a-hook-resolves) above for a complete walkthrough with an annotated example.
153
154<Note>
155 This page uses specific terms for each level: **hook event** for the lifecycle point, **matcher group** for the filter, and **hook handler** for the shell command, HTTP endpoint, prompt, or agent that runs. "Hook" on its own refers to the general feature.
156</Note>
157
158### Hook locations
159
160Where you define a hook determines its scope:
75 161
76### Project-Specific Hook Scripts162| Location | Scope | Shareable |
163| :--------------------------------------------------------- | :---------------------------- | :--------------------------------- |
164| `~/.claude/settings.json` | All your projects | No, local to your machine |
165| `.claude/settings.json` | Single project | Yes, can be committed to the repo |
166| `.claude/settings.local.json` | Single project | No, gitignored |
167| Managed policy settings | Organization-wide | Yes, admin-controlled |
168| [Plugin](/en/plugins) `hooks/hooks.json` | When plugin is enabled | Yes, bundled with the plugin |
169| [Skill](/en/skills) or [agent](/en/sub-agents) frontmatter | While the component is active | Yes, defined in the component file |
77 170
78You can use the environment variable `CLAUDE_PROJECT_DIR` (only available when171For details on settings file resolution, see [settings](/en/settings). Enterprise administrators can use `allowManagedHooksOnly` to block user, project, and plugin hooks. See [Hook configuration](/en/settings#hook-configuration).
79Claude Code spawns the hook command) to reference scripts stored in your project,172
80ensuring they work regardless of Claude's current directory:173### Matcher patterns
174
175The `matcher` field is a regex string that filters when hooks fire. Use `"*"`, `""`, or omit `matcher` entirely to match all occurrences. Each event type matches on a different field:
176
177| Event | What the matcher filters | Example matcher values |
178| :------------------------------------------------------------------------------------------------------------- | :-------------------------------------- | :------------------------------------------------------------------------------------------------------------------------ |
179| `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest` | tool name | `Bash`, `Edit\|Write`, `mcp__.*` |
180| `SessionStart` | how the session started | `startup`, `resume`, `clear`, `compact` |
181| `SessionEnd` | why the session ended | `clear`, `resume`, `logout`, `prompt_input_exit`, `bypass_permissions_disabled`, `other` |
182| `Notification` | notification type | `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog` |
183| `SubagentStart` | agent type | `Bash`, `Explore`, `Plan`, or custom agent names |
184| `PreCompact`, `PostCompact` | what triggered compaction | `manual`, `auto` |
185| `SubagentStop` | agent type | same values as `SubagentStart` |
186| `ConfigChange` | configuration source | `user_settings`, `project_settings`, `local_settings`, `policy_settings`, `skills` |
187| `CwdChanged` | no matcher support | always fires on every directory change |
188| `FileChanged` | filename (basename of the changed file) | `.envrc`, `.env`, any filename you want to watch |
189| `StopFailure` | error type | `rate_limit`, `authentication_failed`, `billing_error`, `invalid_request`, `server_error`, `max_output_tokens`, `unknown` |
190| `InstructionsLoaded` | load reason | `session_start`, `nested_traversal`, `path_glob_match`, `include`, `compact` |
191| `Elicitation` | MCP server name | your configured MCP server names |
192| `ElicitationResult` | MCP server name | same values as `Elicitation` |
193| `UserPromptSubmit`, `Stop`, `TeammateIdle`, `TaskCreated`, `TaskCompleted`, `WorktreeCreate`, `WorktreeRemove` | no matcher support | always fires on every occurrence |
194
195The matcher is a regex, so `Edit|Write` matches either tool and `Notebook.*` matches any tool starting with Notebook. The matcher runs against a field from the [JSON input](#hook-input-and-output) that Claude Code sends to your hook on stdin. For tool events, that field is `tool_name`. Each [hook event](#hook-events) section lists the full set of matcher values and the input schema for that event.
196
197This example runs a linting script only when Claude writes or edits a file:
81 198
82```json theme={null}199```json theme={null}
83{200{
84 "hooks": {201 "hooks": {
85 "PostToolUse": [202 "PostToolUse": [
86 {203 {
87 "matcher": "Write|Edit",204 "matcher": "Edit|Write",
88 "hooks": [205 "hooks": [
89 {206 {
90 "type": "command",207 "type": "command",
91 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-style.sh"208 "command": "/path/to/lint-check.sh"
92 }209 }
93 ]210 ]
94 }211 }
97}214}
98```215```
99 216
100### Plugin hooks217`UserPromptSubmit`, `Stop`, `TeammateIdle`, `TaskCreated`, `TaskCompleted`, `WorktreeCreate`, `WorktreeRemove`, and `CwdChanged` don't support matchers and always fire on every occurrence. If you add a `matcher` field to these events, it is silently ignored.
218
219For tool events, you can filter more narrowly by setting the [`if` field](#common-fields) on individual hook handlers. `if` uses [permission rule syntax](/en/permissions) to match against the tool name and arguments together, so `"Bash(git *)"` runs only for `git` commands and `"Edit(*.ts)"` runs only for TypeScript files.
101 220
102[Plugins](/en/plugins) can provide hooks that integrate seamlessly with your user and project hooks. Plugin hooks are automatically merged with your configuration when plugins are enabled.221#### Match MCP tools
103 222
104**How plugin hooks work**:223[MCP](/en/mcp) server tools appear as regular tools in tool events (`PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`), so you can match them the same way you match any other tool name.
105 224
106* Plugin hooks are defined in the plugin's `hooks/hooks.json` file or in a file given by a custom path to the `hooks` field.225MCP tools follow the naming pattern `mcp__<server>__<tool>`, for example:
107* When a plugin is enabled, its hooks are merged with user and project hooks
108* Multiple hooks from different sources can respond to the same event
109* Plugin hooks use the `${CLAUDE_PLUGIN_ROOT}` environment variable to reference plugin files
110 226
111**Example plugin hook configuration**:227* `mcp__memory__create_entities`: Memory server's create entities tool
228* `mcp__filesystem__read_file`: Filesystem server's read file tool
229* `mcp__github__search_repositories`: GitHub server's search tool
230
231Use regex patterns to target specific MCP tools or groups of tools:
232
233* `mcp__memory__.*` matches all tools from the `memory` server
234* `mcp__.*__write.*` matches any tool containing "write" from any server
235
236This example logs all memory server operations and validates write operations from any MCP server:
112 237
113```json theme={null}238```json theme={null}
114{239{
115 "description": "Automatic code formatting",
116 "hooks": {240 "hooks": {
117 "PostToolUse": [241 "PreToolUse": [
118 {242 {
119 "matcher": "Write|Edit",243 "matcher": "mcp__memory__.*",
120 "hooks": [244 "hooks": [
121 {245 {
122 "type": "command",246 "type": "command",
123 "command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",247 "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
124 "timeout": 30248 }
249 ]
250 },
251 {
252 "matcher": "mcp__.*__write.*",
253 "hooks": [
254 {
255 "type": "command",
256 "command": "/home/user/scripts/validate-mcp-write.py"
125 }257 }
126 ]258 ]
127 }259 }
130}262}
131```263```
132 264
133<Note>265### Hook handler fields
134 Plugin hooks use the same format as regular hooks with an optional `description` field to explain the hook's purpose.
135</Note>
136
137<Note>
138 Plugin hooks run alongside your custom hooks. If multiple hooks match an event, they all execute in parallel.
139</Note>
140
141**Environment variables for plugins**:
142
143* `${CLAUDE_PLUGIN_ROOT}`: Absolute path to the plugin directory
144* `${CLAUDE_PROJECT_DIR}`: Project root directory (same as for project hooks)
145* All standard environment variables are available
146
147See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.
148 266
149### Hooks in Skills, Agents, and Slash Commands267Each object in the inner `hooks` array is a hook handler: the shell command, HTTP endpoint, LLM prompt, or agent that runs when the matcher matches. There are four types:
150 268
151In addition to settings files and plugins, hooks can be defined directly in [Skills](/en/skills), [subagents](/en/sub-agents), and [slash commands](/en/slash-commands) using frontmatter. These hooks are scoped to the component's lifecycle and only run when that component is active.269* **[Command hooks](#command-hook-fields)** (`type: "command"`): run a shell command. Your script receives the event's [JSON input](#hook-input-and-output) on stdin and communicates results back through exit codes and stdout.
270* **[HTTP hooks](#http-hook-fields)** (`type: "http"`): send the event's JSON input as an HTTP POST request to a URL. The endpoint communicates results back through the response body using the same [JSON output format](#json-output) as command hooks.
271* **[Prompt hooks](#prompt-and-agent-hook-fields)** (`type: "prompt"`): send a prompt to a Claude model for single-turn evaluation. The model returns a yes/no decision as JSON. See [Prompt-based hooks](#prompt-based-hooks).
272* **[Agent hooks](#prompt-and-agent-hook-fields)** (`type: "agent"`): spawn a subagent that can use tools like Read, Grep, and Glob to verify conditions before returning a decision. See [Agent-based hooks](#agent-based-hooks).
152 273
153**Supported events**: `PreToolUse`, `PostToolUse`, and `Stop`274#### Common fields
154
155**Example in a Skill**:
156
157```yaml theme={null}
158name: secure-operations
159description: Perform operations with security checks
160hooks:
161 PreToolUse:
162 - matcher: "Bash"
163 hooks:
164 - type: command
165 command: "./scripts/security-check.sh"
166```
167 275
168**Example in an agent**:276These fields apply to all hook types:
169 277
170```yaml theme={null}278| Field | Required | Description |
171name: code-reviewer279| :-------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
172description: Review code changes280| `type` | yes | `"command"`, `"http"`, `"prompt"`, or `"agent"` |
173hooks:281| `if` | no | Permission rule syntax to filter when this hook runs, such as `"Bash(git *)"` or `"Edit(*.ts)"`. The hook only spawns if the tool call matches the pattern. Only evaluated on tool events: `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, and `PermissionRequest`. On other events, a hook with `if` set never runs. Uses the same syntax as [permission rules](/en/permissions) |
174 PostToolUse:282| `timeout` | no | Seconds before canceling. Defaults: 600 for command, 30 for prompt, 60 for agent |
175 - matcher: "Edit|Write"283| `statusMessage` | no | Custom spinner message displayed while the hook runs |
176 hooks:284| `once` | no | If `true`, runs only once per session then is removed. Skills only, not agents. See [Hooks in skills and agents](#hooks-in-skills-and-agents) |
177 - type: command
178 command: "./scripts/run-linter.sh"
179```
180 285
181Component-scoped hooks follow the same configuration format as settings-based hooks but are automatically cleaned up when the component finishes executing.286#### Command hook fields
182 287
183**Additional option for skills and slash commands:**288In addition to the [common fields](#common-fields), command hooks accept these fields:
184 289
185* `once`: Set to `true` to run the hook only once per session. After the first successful execution, the hook is removed. Note: This option is currently only supported for skills and slash commands, not for agents.290| Field | Required | Description |
291| :-------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
292| `command` | yes | Shell command to execute |
293| `async` | no | If `true`, runs in the background without blocking. See [Run hooks in the background](#run-hooks-in-the-background) |
294| `shell` | no | Shell to use for this hook. Accepts `"bash"` (default) or `"powershell"`. Setting `"powershell"` runs the command via PowerShell on Windows. Does not require `CLAUDE_CODE_USE_POWERSHELL_TOOL` since hooks spawn PowerShell directly |
186 295
187## Prompt-Based Hooks296#### HTTP hook fields
188 297
189In addition to bash command hooks (`type: "command"`), Claude Code supports prompt-based hooks (`type: "prompt"`) that use an LLM to evaluate whether to allow or block an action. Prompt-based hooks are currently only supported for `Stop` and `SubagentStop` hooks, where they enable intelligent, context-aware decisions.298In addition to the [common fields](#common-fields), HTTP hooks accept these fields:
190 299
191### How prompt-based hooks work300| Field | Required | Description |
301| :--------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
302| `url` | yes | URL to send the POST request to |
303| `headers` | no | Additional HTTP headers as key-value pairs. Values support environment variable interpolation using `$VAR_NAME` or `${VAR_NAME}` syntax. Only variables listed in `allowedEnvVars` are resolved |
304| `allowedEnvVars` | no | List of environment variable names that may be interpolated into header values. References to unlisted variables are replaced with empty strings. Required for any env var interpolation to work |
192 305
193Instead of executing a bash command, prompt-based hooks:306Claude Code sends the hook's [JSON input](#hook-input-and-output) as the POST request body with `Content-Type: application/json`. The response body uses the same [JSON output format](#json-output) as command hooks.
194 307
1951. Send the hook input and your prompt to a fast LLM (Haiku)308Error handling differs from command hooks: non-2xx responses, connection failures, and timeouts all produce non-blocking errors that allow execution to continue. To block a tool call or deny a permission, return a 2xx response with a JSON body containing `decision: "block"` or a `hookSpecificOutput` with `permissionDecision: "deny"`.
1962. The LLM responds with structured JSON containing a decision
1973. Claude Code processes the decision automatically
198 309
199### Configuration310This example sends `PreToolUse` events to a local validation service, authenticating with a token from the `MY_TOKEN` environment variable:
200 311
201```json theme={null}312```json theme={null}
202{313{
203 "hooks": {314 "hooks": {
204 "Stop": [315 "PreToolUse": [
205 {316 {
317 "matcher": "Bash",
206 "hooks": [318 "hooks": [
207 {319 {
208 "type": "prompt",320 "type": "http",
209 "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."321 "url": "http://localhost:8080/hooks/pre-tool-use",
322 "timeout": 30,
323 "headers": {
324 "Authorization": "Bearer $MY_TOKEN"
325 },
326 "allowedEnvVars": ["MY_TOKEN"]
210 }327 }
211 ]328 ]
212 }329 }
219}332}
220```333```
221 334
222**Fields:**335#### Prompt and agent hook fields
223
224* `type`: Must be `"prompt"`
225* `prompt`: The prompt text to send to the LLM
226 * Use `$ARGUMENTS` as a placeholder for the hook input JSON
227 * If `$ARGUMENTS` is not present, input JSON is appended to the prompt
228* `timeout`: (Optional) Timeout in seconds (default: 30 seconds)
229
230### Response schema
231
232The LLM must respond with JSON containing:
233 336
234```json theme={null}337In addition to the [common fields](#common-fields), prompt and agent hooks accept these fields:
235{
236 "ok": true | false,
237 "reason": "Explanation for the decision"
238}
239```
240 338
241**Response fields:**339| Field | Required | Description |
340| :------- | :------- | :------------------------------------------------------------------------------------------ |
341| `prompt` | yes | Prompt text to send to the model. Use `$ARGUMENTS` as a placeholder for the hook input JSON |
342| `model` | no | Model to use for evaluation. Defaults to a fast model |
242 343
243* `ok`: `true` allows the action, `false` prevents it344All matching hooks run in parallel, and identical handlers are deduplicated automatically. Command hooks are deduplicated by command string, and HTTP hooks are deduplicated by URL. Handlers run in the current directory with Claude Code's environment. The `$CLAUDE_CODE_REMOTE` environment variable is set to `"true"` in remote web environments and not set in the local CLI.
244* `reason`: Required when `ok` is `false`. Explanation shown to Claude
245 345
246### Supported hook events346### Reference scripts by path
247 347
248Prompt-based hooks work with any hook event, but are most useful for:348Use environment variables to reference hook scripts relative to the project or plugin root, regardless of the working directory when the hook runs:
249 349
250* **Stop**: Intelligently decide if Claude should continue working350* `$CLAUDE_PROJECT_DIR`: the project root. Wrap in quotes to handle paths with spaces.
251* **SubagentStop**: Evaluate if a subagent has completed its task351* `${CLAUDE_PLUGIN_ROOT}`: the plugin's installation directory, for scripts bundled with a [plugin](/en/plugins). Changes on each plugin update.
252* **UserPromptSubmit**: Validate user prompts with LLM assistance352* `${CLAUDE_PLUGIN_DATA}`: the plugin's [persistent data directory](/en/plugins-reference#persistent-data-directory), for dependencies and state that should survive plugin updates.
253* **PreToolUse**: Make context-aware permission decisions
254* **PermissionRequest**: Intelligently allow or deny permission dialogs
255 353
256### Example: Intelligent Stop hook354<Tabs>
355 <Tab title="Project scripts">
356 This example uses `$CLAUDE_PROJECT_DIR` to run a style checker from the project's `.claude/hooks/` directory after any `Write` or `Edit` tool call:
257 357
258```json theme={null}358 ```json theme={null}
259{359 {
260 "hooks": {360 "hooks": {
261 "Stop": [361 "PostToolUse": [
262 {362 {
363 "matcher": "Write|Edit",
263 "hooks": [364 "hooks": [
264 {365 {
265 "type": "prompt",366 "type": "command",
266 "prompt": "You are evaluating whether Claude should stop working. Context: $ARGUMENTS\n\nAnalyze the conversation and determine if:\n1. All user-requested tasks are complete\n2. Any errors need to be addressed\n3. Follow-up work is needed\n\nRespond with JSON: {\"ok\": true} to allow stopping, or {\"ok\": false, \"reason\": \"your explanation\"} to continue working.",367 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-style.sh"
267 "timeout": 30
268 }368 }
269 ]369 ]
270 }370 }
271 ]371 ]
272 }372 }
273}373 }
274```374 ```
375 </Tab>
275 376
276### Example: SubagentStop with custom logic377 <Tab title="Plugin scripts">
378 Define plugin hooks in `hooks/hooks.json` with an optional top-level `description` field. When a plugin is enabled, its hooks merge with your user and project hooks.
277 379
278```json theme={null}380 This example runs a formatting script bundled with the plugin:
279{381
382 ```json theme={null}
383 {
384 "description": "Automatic code formatting",
280 "hooks": {385 "hooks": {
281 "SubagentStop": [386 "PostToolUse": [
282 {387 {
388 "matcher": "Write|Edit",
283 "hooks": [389 "hooks": [
284 {390 {
285 "type": "prompt",391 "type": "command",
286 "prompt": "Evaluate if this subagent should stop. Input: $ARGUMENTS\n\nCheck if:\n- The subagent completed its assigned task\n- Any errors occurred that need fixing\n- Additional context gathering is needed\n\nReturn: {\"ok\": true} to allow stopping, or {\"ok\": false, \"reason\": \"explanation\"} to continue."392 "command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",
393 "timeout": 30
287 }394 }
288 ]395 ]
289 }396 }
290 ]397 ]
291 }398 }
292}399 }
293```400 ```
294 401
295### Comparison with bash command hooks402 See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.
403 </Tab>
404</Tabs>
296 405
297| Feature | Bash Command Hooks | Prompt-Based Hooks |406### Hooks in skills and agents
298| --------------------- | ----------------------- | ------------------------------ |
299| **Execution** | Runs bash script | Queries LLM |
300| **Decision logic** | You implement in code | LLM evaluates context |
301| **Setup complexity** | Requires script file | Configure prompt |
302| **Context awareness** | Limited to script logic | Natural language understanding |
303| **Performance** | Fast (local execution) | Slower (API call) |
304| **Use case** | Deterministic rules | Context-aware decisions |
305 407
306### Best practices408In addition to settings files and plugins, hooks can be defined directly in [skills](/en/skills) and [subagents](/en/sub-agents) using frontmatter. These hooks are scoped to the component's lifecycle and only run when that component is active.
307 409
308* **Be specific in prompts**: Clearly state what you want the LLM to evaluate410All hook events are supported. For subagents, `Stop` hooks are automatically converted to `SubagentStop` since that is the event that fires when a subagent completes.
309* **Include decision criteria**: List the factors the LLM should consider
310* **Test your prompts**: Verify the LLM makes correct decisions for your use cases
311* **Set appropriate timeouts**: Default is 30 seconds, adjust if needed
312* **Use for complex decisions**: Bash hooks are better for simple, deterministic rules
313 411
314See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.412Hooks use the same configuration format as settings-based hooks but are scoped to the component's lifetime and cleaned up when it finishes.
315 413
316## Hook Events414This skill defines a `PreToolUse` hook that runs a security validation script before each `Bash` command:
317 415
318### PreToolUse416```yaml theme={null}
417---
418name: secure-operations
419description: Perform operations with security checks
420hooks:
421 PreToolUse:
422 - matcher: "Bash"
423 hooks:
424 - type: command
425 command: "./scripts/security-check.sh"
426---
427```
319 428
320Runs after Claude creates tool parameters and before processing the tool call.429Agents use the same format in their YAML frontmatter.
321 430
322**Common matchers:**431### The `/hooks` menu
323 432
324* `Task` - Subagent tasks (see [subagents documentation](/en/sub-agents))433Type `/hooks` in Claude Code to open a read-only browser for your configured hooks. The menu shows every hook event with a count of configured hooks, lets you drill into matchers, and shows the full details of each hook handler. Use it to verify configuration, check which settings file a hook came from, or inspect a hook's command, prompt, or URL.
325* `Bash` - Shell commands
326* `Glob` - File pattern matching
327* `Grep` - Content search
328* `Read` - File reading
329* `Edit` - File editing
330* `Write` - File writing
331* `WebFetch`, `WebSearch` - Web operations
332 434
333Use [PreToolUse decision control](#pretooluse-decision-control) to allow, deny, or ask for permission to use the tool.435The menu displays all four hook types: `command`, `prompt`, `agent`, and `http`. Each hook is labeled with a `[type]` prefix and a source indicating where it was defined:
334 436
335### PermissionRequest437* `User`: from `~/.claude/settings.json`
438* `Project`: from `.claude/settings.json`
439* `Local`: from `.claude/settings.local.json`
440* `Plugin`: from a plugin's `hooks/hooks.json`
441* `Session`: registered in memory for the current session
442* `Built-in`: registered internally by Claude Code
336 443
337Runs when the user is shown a permission dialog.444Selecting a hook opens a detail view showing its event, matcher, type, source file, and the full command, prompt, or URL. The menu is read-only: to add, modify, or remove hooks, edit the settings JSON directly or ask Claude to make the change.
338Use [PermissionRequest decision control](#permissionrequest-decision-control) to allow or deny on behalf of the user.
339 445
340Recognizes the same matcher values as PreToolUse.446### Disable or remove hooks
341 447
342### PostToolUse448To remove a hook, delete its entry from the settings JSON file.
343 449
344Runs immediately after a tool completes successfully.450To temporarily disable all hooks without removing them, set `"disableAllHooks": true` in your settings file. There is no way to disable an individual hook while keeping it in the configuration.
345 451
346Recognizes the same matcher values as PreToolUse.452The `disableAllHooks` setting respects the managed settings hierarchy. If an administrator has configured hooks through managed policy settings, `disableAllHooks` set in user, project, or local settings cannot disable those managed hooks. Only `disableAllHooks` set at the managed settings level can disable managed hooks.
347 453
348### Notification454Direct edits to hooks in settings files are normally picked up automatically by the file watcher.
349 455
350Runs when Claude Code sends notifications. Supports matchers to filter by notification type.456## Hook input and output
351 457
352**Common matchers:**458Command hooks receive JSON data via stdin and communicate results through exit codes, stdout, and stderr. HTTP hooks receive the same JSON as the POST request body and communicate results through the HTTP response body. This section covers fields and behavior common to all events. Each event's section under [Hook events](#hook-events) includes its specific input schema and decision control options.
353 459
354* `permission_prompt` - Permission requests from Claude Code460### Common input fields
355* `idle_prompt` - When Claude is waiting for user input (after 60+ seconds of idle time)
356* `auth_success` - Authentication success notifications
357* `elicitation_dialog` - When Claude Code needs input for MCP tool elicitation
358 461
359You can use matchers to run different hooks for different notification types, or omit the matcher to run hooks for all notifications.462Hook events receive these fields as JSON, in addition to event-specific fields documented in each [hook event](#hook-events) section. For command hooks, this JSON arrives via stdin. For HTTP hooks, it arrives as the POST request body.
360 463
361**Example: Different notifications for different types**464| Field | Description |
465| :---------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
466| `session_id` | Current session identifier |
467| `transcript_path` | Path to conversation JSON |
468| `cwd` | Current working directory when the hook is invoked |
469| `permission_mode` | Current [permission mode](/en/permissions#permission-modes): `"default"`, `"plan"`, `"acceptEdits"`, `"auto"`, `"dontAsk"`, or `"bypassPermissions"`. Not all events receive this field: see each event's JSON example below to check |
470| `hook_event_name` | Name of the event that fired |
471
472When running with `--agent` or inside a subagent, two additional fields are included:
473
474| Field | Description |
475| :----------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
476| `agent_id` | Unique identifier for the subagent. Present only when the hook fires inside a subagent call. Use this to distinguish subagent hook calls from main-thread calls. |
477| `agent_type` | Agent name (for example, `"Explore"` or `"security-reviewer"`). Present when the session uses `--agent` or the hook fires inside a subagent. For subagents, the subagent's type takes precedence over the session's `--agent` value. |
478
479For example, a `PreToolUse` hook for a Bash command receives this on stdin:
362 480
363```json theme={null}481```json theme={null}
364{482{
365 "hooks": {483 "session_id": "abc123",
366 "Notification": [484 "transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
367 {485 "cwd": "/home/user/my-project",
368 "matcher": "permission_prompt",486 "permission_mode": "default",
369 "hooks": [487 "hook_event_name": "PreToolUse",
370 {488 "tool_name": "Bash",
371 "type": "command",489 "tool_input": {
372 "command": "/path/to/permission-alert.sh"490 "command": "npm test"
373 }
374 ]
375 },
376 {
377 "matcher": "idle_prompt",
378 "hooks": [
379 {
380 "type": "command",
381 "command": "/path/to/idle-notification.sh"
382 }
383 ]
384 }
385 ]
386 }491 }
387}492}
388```493```
389 494
390### UserPromptSubmit495The `tool_name` and `tool_input` fields are event-specific. Each [hook event](#hook-events) section documents the additional fields for that event.
391
392Runs when the user submits a prompt, before Claude processes it. This allows you
393to add additional context based on the prompt/conversation, validate prompts, or
394block certain types of prompts.
395
396### Stop
397
398Runs when the main Claude Code agent has finished responding. Does not run if
399the stoppage occurred due to a user interrupt.
400
401### SubagentStop
402
403Runs when a Claude Code subagent (Task tool call) has finished responding.
404
405### PreCompact
406
407Runs before Claude Code is about to run a compact operation.
408
409**Matchers:**
410
411* `manual` - Invoked from `/compact`
412* `auto` - Invoked from auto-compact (due to full context window)
413
414### SessionStart
415 496
416Runs when Claude Code starts a new session or resumes an existing session (which497### Exit code output
417currently does start a new session under the hood). Useful for loading in
418development context like existing issues or recent changes to your codebase, installing dependencies, or setting up environment variables.
419 498
420**Matchers:**499The exit code from your hook command tells Claude Code whether the action should proceed, be blocked, or be ignored.
421 500
422* `startup` - Invoked from startup501**Exit 0** means success. Claude Code parses stdout for [JSON output fields](#json-output). JSON output is only processed on exit 0. For most events, stdout is only shown in verbose mode (`Ctrl+O`). The exceptions are `UserPromptSubmit` and `SessionStart`, where stdout is added as context that Claude can see and act on.
423* `resume` - Invoked from `--resume`, `--continue`, or `/resume`
424* `clear` - Invoked from `/clear`
425* `compact` - Invoked from auto or manual compact.
426 502
427#### Persisting environment variables503**Exit 2** means a blocking error. Claude Code ignores stdout and any JSON in it. Instead, stderr text is fed back to Claude as an error message. The effect depends on the event: `PreToolUse` blocks the tool call, `UserPromptSubmit` rejects the prompt, and so on. See [exit code 2 behavior](#exit-code-2-behavior-per-event) for the full list.
428 504
429SessionStart hooks have access to the `CLAUDE_ENV_FILE` environment variable, which provides a file path where you can persist environment variables for subsequent bash commands.505**Any other exit code** is a non-blocking error. stderr is shown in verbose mode (`Ctrl+O`) and execution continues.
430 506
431**Example: Setting individual environment variables**507For example, a hook command script that blocks dangerous Bash commands:
432 508
433```bash theme={null}509```bash theme={null}
434#!/bin/bash510#!/bin/bash
511# Reads JSON input from stdin, checks the command
512command=$(jq -r '.tool_input.command' < /dev/stdin)
435 513
436if [ -n "$CLAUDE_ENV_FILE" ]; then514if [[ "$command" == rm* ]]; then
437 echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"515 echo "Blocked: rm commands are not allowed" >&2
438 echo 'export API_KEY=your-api-key' >> "$CLAUDE_ENV_FILE"516 exit 2 # Blocking error: tool call is prevented
439 echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
440fi517fi
441 518
442exit 0519exit 0 # Success: tool call proceeds
443```520```
444 521
445**Example: Persisting all environment changes from the hook**522#### Exit code 2 behavior per event
523
524Exit code 2 is the way a hook signals "stop, don't do this." The effect depends on the event, because some events represent actions that can be blocked (like a tool call that hasn't happened yet) and others represent things that already happened or can't be prevented.
525
526| Hook event | Can block? | What happens on exit 2 |
527| :------------------- | :--------- | :---------------------------------------------------------------------------- |
528| `PreToolUse` | Yes | Blocks the tool call |
529| `PermissionRequest` | Yes | Denies the permission |
530| `UserPromptSubmit` | Yes | Blocks prompt processing and erases the prompt |
531| `Stop` | Yes | Prevents Claude from stopping, continues the conversation |
532| `SubagentStop` | Yes | Prevents the subagent from stopping |
533| `TeammateIdle` | Yes | Prevents the teammate from going idle (teammate continues working) |
534| `TaskCreated` | Yes | Prevents the task from being created |
535| `TaskCompleted` | Yes | Prevents the task from being marked as completed |
536| `ConfigChange` | Yes | Blocks the configuration change from taking effect (except `policy_settings`) |
537| `StopFailure` | No | Output and exit code are ignored |
538| `PostToolUse` | No | Shows stderr to Claude (tool already ran) |
539| `PostToolUseFailure` | No | Shows stderr to Claude (tool already failed) |
540| `Notification` | No | Shows stderr to user only |
541| `SubagentStart` | No | Shows stderr to user only |
542| `SessionStart` | No | Shows stderr to user only |
543| `SessionEnd` | No | Shows stderr to user only |
544| `CwdChanged` | No | Shows stderr to user only |
545| `FileChanged` | No | Shows stderr to user only |
546| `PreCompact` | No | Shows stderr to user only |
547| `PostCompact` | No | Shows stderr to user only |
548| `Elicitation` | Yes | Denies the elicitation |
549| `ElicitationResult` | Yes | Blocks the response (action becomes decline) |
550| `WorktreeCreate` | Yes | Any non-zero exit code causes worktree creation to fail |
551| `WorktreeRemove` | No | Failures are logged in debug mode only |
552| `InstructionsLoaded` | No | Exit code is ignored |
553
554### HTTP response handling
555
556HTTP hooks use HTTP status codes and response bodies instead of exit codes and stdout:
557
558* **2xx with an empty body**: success, equivalent to exit code 0 with no output
559* **2xx with a plain text body**: success, the text is added as context
560* **2xx with a JSON body**: success, parsed using the same [JSON output](#json-output) schema as command hooks
561* **Non-2xx status**: non-blocking error, execution continues
562* **Connection failure or timeout**: non-blocking error, execution continues
563
564Unlike command hooks, HTTP hooks cannot signal a blocking error through status codes alone. To block a tool call or deny a permission, return a 2xx response with a JSON body containing the appropriate decision fields.
565
566### JSON output
567
568Exit codes let you allow or block, but JSON output gives you finer-grained control. Instead of exiting with code 2 to block, exit 0 and print a JSON object to stdout. Claude Code reads specific fields from that JSON to control behavior, including [decision control](#decision-control) for blocking, allowing, or escalating to the user.
446 569
447When your setup modifies the environment (for example, `nvm use`), capture and persist all changes by diffing the environment:570<Note>
571 You must choose one approach per hook, not both: either use exit codes alone for signaling, or exit 0 and print JSON for structured control. Claude Code only processes JSON on exit 0. If you exit 2, any JSON is ignored.
572</Note>
448 573
449```bash theme={null}574Your hook's stdout must contain only the JSON object. If your shell profile prints text on startup, it can interfere with JSON parsing. See [JSON validation failed](/en/hooks-guide#json-validation-failed) in the troubleshooting guide.
450#!/bin/bash
451 575
452ENV_BEFORE=$(export -p | sort)576The JSON object supports three kinds of fields:
453 577
454# Run your setup commands that modify the environment578* **Universal fields** like `continue` work across all events. These are listed in the table below.
455source ~/.nvm/nvm.sh579* **Top-level `decision` and `reason`** are used by some events to block or provide feedback.
456nvm use 20580* **`hookSpecificOutput`** is a nested object for events that need richer control. It requires a `hookEventName` field set to the event name.
457 581
458if [ -n "$CLAUDE_ENV_FILE" ]; then582| Field | Default | Description |
459 ENV_AFTER=$(export -p | sort)583| :--------------- | :------ | :------------------------------------------------------------------------------------------------------------------------- |
460 comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"584| `continue` | `true` | If `false`, Claude stops processing entirely after the hook runs. Takes precedence over any event-specific decision fields |
461fi585| `stopReason` | none | Message shown to the user when `continue` is `false`. Not shown to Claude |
586| `suppressOutput` | `false` | If `true`, hides stdout from verbose mode output |
587| `systemMessage` | none | Warning message shown to the user |
462 588
463exit 0589To stop Claude entirely regardless of event type:
464```
465 590
466Any variables written to this file will be available in all subsequent bash commands that Claude Code executes during the session.591```json theme={null}
592{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }
593```
467 594
468<Note>595#### Decision control
469 `CLAUDE_ENV_FILE` is only available for SessionStart hooks. Other hook types do not have access to this variable.
470</Note>
471 596
472### SessionEnd597Not every event supports blocking or controlling behavior through JSON. The events that do each use a different set of fields to express that decision. Use this table as a quick reference before writing a hook:
473 598
474Runs when a Claude Code session ends. Useful for cleanup tasks, logging session599| Events | Decision pattern | Key fields |
475statistics, or saving session state.600| :-------------------------------------------------------------------------------------------------------------------------- | :----------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
601| UserPromptSubmit, PostToolUse, PostToolUseFailure, Stop, SubagentStop, ConfigChange | Top-level `decision` | `decision: "block"`, `reason` |
602| TeammateIdle, TaskCreated, TaskCompleted | Exit code or `continue: false` | Exit code 2 blocks the action with stderr feedback. JSON `{"continue": false, "stopReason": "..."}` also stops the teammate entirely, matching `Stop` hook behavior |
603| PreToolUse | `hookSpecificOutput` | `permissionDecision` (allow/deny/ask), `permissionDecisionReason` |
604| PermissionRequest | `hookSpecificOutput` | `decision.behavior` (allow/deny) |
605| WorktreeCreate | path return | Command hook prints path on stdout; HTTP hook returns `hookSpecificOutput.worktreePath`. Hook failure or missing path fails creation |
606| Elicitation | `hookSpecificOutput` | `action` (accept/decline/cancel), `content` (form field values for accept) |
607| ElicitationResult | `hookSpecificOutput` | `action` (accept/decline/cancel), `content` (form field values override) |
608| WorktreeRemove, Notification, SessionEnd, PreCompact, PostCompact, InstructionsLoaded, StopFailure, CwdChanged, FileChanged | None | No decision control. Used for side effects like logging or cleanup |
476 609
477The `reason` field in the hook input will be one of:610Here are examples of each pattern in action:
478 611
479* `clear` - Session cleared with /clear command612<Tabs>
480* `logout` - User logged out613 <Tab title="Top-level decision">
481* `prompt_input_exit` - User exited while prompt input was visible614 Used by `UserPromptSubmit`, `PostToolUse`, `PostToolUseFailure`, `Stop`, `SubagentStop`, and `ConfigChange`. The only value is `"block"`. To allow the action to proceed, omit `decision` from your JSON, or exit 0 without any JSON at all:
482* `other` - Other exit reasons
483 615
484## Hook Input616 ```json theme={null}
617 {
618 "decision": "block",
619 "reason": "Test suite must pass before proceeding"
620 }
621 ```
622 </Tab>
485 623
486Hooks receive JSON data via stdin containing session information and624 <Tab title="PreToolUse">
487event-specific data:625 Uses `hookSpecificOutput` for richer control: allow, deny, or escalate to the user. You can also modify tool input before it runs or inject additional context for Claude. See [PreToolUse decision control](#pretooluse-decision-control) for the full set of options.
488 626
489```typescript theme={null}627 ```json theme={null}
490{628 {
491 // Common fields629 "hookSpecificOutput": {
492 session_id: string630 "hookEventName": "PreToolUse",
493 transcript_path: string // Path to conversation JSON631 "permissionDecision": "deny",
494 cwd: string // The current working directory when the hook is invoked632 "permissionDecisionReason": "Database writes are not allowed"
495 permission_mode: string // Current permission mode: "default", "plan", "acceptEdits", "dontAsk", or "bypassPermissions"633 }
496 634 }
497 // Event-specific fields635 ```
498 hook_event_name: string636 </Tab>
499 ...
500}
501```
502 637
503### PreToolUse Input638 <Tab title="PermissionRequest">
639 Uses `hookSpecificOutput` to allow or deny a permission request on behalf of the user. When allowing, you can also modify the tool's input or apply permission rules so the user isn't prompted again. See [PermissionRequest decision control](#permissionrequest-decision-control) for the full set of options.
504 640
505The exact schema for `tool_input` depends on the tool. Here are examples for commonly hooked tools.641 ```json theme={null}
642 {
643 "hookSpecificOutput": {
644 "hookEventName": "PermissionRequest",
645 "decision": {
646 "behavior": "allow",
647 "updatedInput": {
648 "command": "npm run lint"
649 }
650 }
651 }
652 }
653 ```
654 </Tab>
655</Tabs>
656
657For extended examples including Bash command validation, prompt filtering, and auto-approval scripts, see [What you can automate](/en/hooks-guide#what-you-can-automate) in the guide and the [Bash command validator reference implementation](https://github.com/anthropics/claude-code/blob/main/examples/hooks/bash_command_validator_example.py).
658
659## Hook events
660
661Each event corresponds to a point in Claude Code's lifecycle where hooks can run. The sections below are ordered to match the lifecycle: from session setup through the agentic loop to session end. Each section describes when the event fires, what matchers it supports, the JSON input it receives, and how to control behavior through output.
662
663### SessionStart
506 664
507#### Bash tool665Runs when Claude Code starts a new session or resumes an existing session. Useful for loading development context like existing issues or recent changes to your codebase, or setting up environment variables. For static context that does not require a script, use [CLAUDE.md](/en/memory) instead.
508 666
509The Bash tool is the most commonly hooked tool for command validation:667SessionStart runs on every session, so keep these hooks fast. Only `type: "command"` hooks are supported.
668
669The matcher value corresponds to how the session was initiated:
670
671| Matcher | When it fires |
672| :-------- | :------------------------------------- |
673| `startup` | New session |
674| `resume` | `--resume`, `--continue`, or `/resume` |
675| `clear` | `/clear` |
676| `compact` | Auto or manual compaction |
677
678#### SessionStart input
679
680In addition to the [common input fields](#common-input-fields), SessionStart hooks receive `source`, `model`, and optionally `agent_type`. The `source` field indicates how the session started: `"startup"` for new sessions, `"resume"` for resumed sessions, `"clear"` after `/clear`, or `"compact"` after compaction. The `model` field contains the model identifier. If you start Claude Code with `claude --agent <name>`, an `agent_type` field contains the agent name.
510 681
511```json theme={null}682```json theme={null}
512{683{
513 "session_id": "abc123",684 "session_id": "abc123",
514 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",685 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
515 "cwd": "/Users/...",686 "cwd": "/Users/...",
516 "permission_mode": "default",687 "hook_event_name": "SessionStart",
517 "hook_event_name": "PreToolUse",688 "source": "startup",
518 "tool_name": "Bash",689 "model": "claude-sonnet-4-6"
519 "tool_input": {690}
520 "command": "psql -c 'SELECT * FROM users'",691```
521 "description": "Query the users table",692
522 "timeout": 120000693#### SessionStart decision control
523 },694
524 "tool_use_id": "toolu_01ABC123..."695Any text your hook script prints to stdout is added as context for Claude. In addition to the [JSON output fields](#json-output) available to all hooks, you can return these event-specific fields:
696
697| Field | Description |
698| :------------------ | :------------------------------------------------------------------------ |
699| `additionalContext` | String added to Claude's context. Multiple hooks' values are concatenated |
700
701```json theme={null}
702{
703 "hookSpecificOutput": {
704 "hookEventName": "SessionStart",
705 "additionalContext": "My additional context here"
706 }
525}707}
526```708```
527 709
528| Field | Type | Description |710#### Persist environment variables
529| :------------------ | :------ | :-------------------------------------------- |711
530| `command` | string | The shell command to execute |712SessionStart hooks have access to the `CLAUDE_ENV_FILE` environment variable, which provides a file path where you can persist environment variables for subsequent Bash commands.
531| `description` | string | Optional description of what the command does |713
532| `timeout` | number | Optional timeout in milliseconds |714To set individual environment variables, write `export` statements to `CLAUDE_ENV_FILE`. Use append (`>>`) to preserve variables set by other hooks:
533| `run_in_background` | boolean | Whether to run the command in background |715
716```bash theme={null}
717#!/bin/bash
718
719if [ -n "$CLAUDE_ENV_FILE" ]; then
720 echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
721 echo 'export DEBUG_LOG=true' >> "$CLAUDE_ENV_FILE"
722 echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
723fi
724
725exit 0
726```
727
728To capture all environment changes from setup commands, compare the exported variables before and after:
729
730```bash theme={null}
731#!/bin/bash
732
733ENV_BEFORE=$(export -p | sort)
734
735# Run your setup commands that modify the environment
736source ~/.nvm/nvm.sh
737nvm use 20
738
739if [ -n "$CLAUDE_ENV_FILE" ]; then
740 ENV_AFTER=$(export -p | sort)
741 comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"
742fi
743
744exit 0
745```
746
747Any variables written to this file will be available in all subsequent Bash commands that Claude Code executes during the session.
748
749<Note>
750 `CLAUDE_ENV_FILE` is available for SessionStart, [CwdChanged](#cwdchanged), and [FileChanged](#filechanged) hooks. Other hook types do not have access to this variable.
751</Note>
752
753### InstructionsLoaded
754
755Fires when a `CLAUDE.md` or `.claude/rules/*.md` file is loaded into context. This event fires at session start for eagerly-loaded files and again later when files are lazily loaded, for example when Claude accesses a subdirectory that contains a nested `CLAUDE.md` or when conditional rules with `paths:` frontmatter match. The hook does not support blocking or decision control. It runs asynchronously for observability purposes.
534 756
535#### Write tool757The matcher runs against `load_reason`. For example, use `"matcher": "session_start"` to fire only for files loaded at session start, or `"matcher": "path_glob_match|nested_traversal"` to fire only for lazy loads.
758
759#### InstructionsLoaded input
760
761In addition to the [common input fields](#common-input-fields), InstructionsLoaded hooks receive these fields:
762
763| Field | Description |
764| :------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
765| `file_path` | Absolute path to the instruction file that was loaded |
766| `memory_type` | Scope of the file: `"User"`, `"Project"`, `"Local"`, or `"Managed"` |
767| `load_reason` | Why the file was loaded: `"session_start"`, `"nested_traversal"`, `"path_glob_match"`, `"include"`, or `"compact"`. The `"compact"` value fires when instruction files are re-loaded after a compaction event |
768| `globs` | Path glob patterns from the file's `paths:` frontmatter, if any. Present only for `path_glob_match` loads |
769| `trigger_file_path` | Path to the file whose access triggered this load, for lazy loads |
770| `parent_file_path` | Path to the parent instruction file that included this one, for `include` loads |
536 771
537```json theme={null}772```json theme={null}
538{773{
539 "session_id": "abc123",774 "session_id": "abc123",
540 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",775 "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
541 "cwd": "/Users/...",776 "cwd": "/Users/my-project",
542 "permission_mode": "default",777 "hook_event_name": "InstructionsLoaded",
543 "hook_event_name": "PreToolUse",778 "file_path": "/Users/my-project/CLAUDE.md",
544 "tool_name": "Write",779 "memory_type": "Project",
545 "tool_input": {780 "load_reason": "session_start"
546 "file_path": "/path/to/file.txt",
547 "content": "file content"
548 },
549 "tool_use_id": "toolu_01ABC123..."
550}781}
551```782```
552 783
553| Field | Type | Description |784#### InstructionsLoaded decision control
554| :---------- | :----- | :--------------------------------- |785
555| `file_path` | string | Absolute path to the file to write |786InstructionsLoaded hooks have no decision control. They cannot block or modify instruction loading. Use this event for audit logging, compliance tracking, or observability.
556| `content` | string | Content to write to the file |787
788### UserPromptSubmit
789
790Runs when the user submits a prompt, before Claude processes it. This allows you
791to add additional context based on the prompt/conversation, validate prompts, or
792block certain types of prompts.
793
794#### UserPromptSubmit input
557 795
558#### Edit tool796In addition to the [common input fields](#common-input-fields), UserPromptSubmit hooks receive the `prompt` field containing the text the user submitted.
559 797
560```json theme={null}798```json theme={null}
561{799{
563 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",801 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
564 "cwd": "/Users/...",802 "cwd": "/Users/...",
565 "permission_mode": "default",803 "permission_mode": "default",
566 "hook_event_name": "PreToolUse",804 "hook_event_name": "UserPromptSubmit",
567 "tool_name": "Edit",805 "prompt": "Write a function to calculate the factorial of a number"
568 "tool_input": {806}
569 "file_path": "/path/to/file.txt",807```
570 "old_string": "original text",808
571 "new_string": "replacement text"809#### UserPromptSubmit decision control
810
811`UserPromptSubmit` hooks can control whether a user prompt is processed and add context. All [JSON output fields](#json-output) are available.
812
813There are two ways to add context to the conversation on exit code 0:
814
815* **Plain text stdout**: any non-JSON text written to stdout is added as context
816* **JSON with `additionalContext`**: use the JSON format below for more control. The `additionalContext` field is added as context
817
818Plain stdout is shown as hook output in the transcript. The `additionalContext` field is added more discretely.
819
820To block a prompt, return a JSON object with `decision` set to `"block"`:
821
822| Field | Description |
823| :------------------ | :----------------------------------------------------------------------------------------------------------------- |
824| `decision` | `"block"` prevents the prompt from being processed and erases it from context. Omit to allow the prompt to proceed |
825| `reason` | Shown to the user when `decision` is `"block"`. Not added to context |
826| `additionalContext` | String added to Claude's context |
827
828```json theme={null}
829{
830 "decision": "block",
831 "reason": "Explanation for decision",
832 "hookSpecificOutput": {
833 "hookEventName": "UserPromptSubmit",
834 "additionalContext": "My additional context here"
835 }
836}
837```
838
839<Note>
840 The JSON format isn't required for simple use cases. To add context, you can print plain text to stdout with exit code 0. Use JSON when you need to
841 block prompts or want more structured control.
842</Note>
843
844### PreToolUse
845
846Runs after Claude creates tool parameters and before processing the tool call. Matches on tool name: `Bash`, `Edit`, `Write`, `Read`, `Glob`, `Grep`, `Agent`, `WebFetch`, `WebSearch`, `AskUserQuestion`, `ExitPlanMode`, and any [MCP tool names](#match-mcp-tools).
847
848Use [PreToolUse decision control](#pretooluse-decision-control) to allow, deny, or ask for permission to use the tool.
849
850#### PreToolUse input
851
852In addition to the [common input fields](#common-input-fields), PreToolUse hooks receive `tool_name`, `tool_input`, and `tool_use_id`. The `tool_input` fields depend on the tool:
853
854##### Bash
855
856Executes shell commands.
857
858| Field | Type | Example | Description |
859| :------------------ | :------ | :----------------- | :-------------------------------------------- |
860| `command` | string | `"npm test"` | The shell command to execute |
861| `description` | string | `"Run test suite"` | Optional description of what the command does |
862| `timeout` | number | `120000` | Optional timeout in milliseconds |
863| `run_in_background` | boolean | `false` | Whether to run the command in background |
864
865##### Write
866
867Creates or overwrites a file.
868
869| Field | Type | Example | Description |
870| :---------- | :----- | :-------------------- | :--------------------------------- |
871| `file_path` | string | `"/path/to/file.txt"` | Absolute path to the file to write |
872| `content` | string | `"file content"` | Content to write to the file |
873
874##### Edit
875
876Replaces a string in an existing file.
877
878| Field | Type | Example | Description |
879| :------------ | :------ | :-------------------- | :--------------------------------- |
880| `file_path` | string | `"/path/to/file.txt"` | Absolute path to the file to edit |
881| `old_string` | string | `"original text"` | Text to find and replace |
882| `new_string` | string | `"replacement text"` | Replacement text |
883| `replace_all` | boolean | `false` | Whether to replace all occurrences |
884
885##### Read
886
887Reads file contents.
888
889| Field | Type | Example | Description |
890| :---------- | :----- | :-------------------- | :----------------------------------------- |
891| `file_path` | string | `"/path/to/file.txt"` | Absolute path to the file to read |
892| `offset` | number | `10` | Optional line number to start reading from |
893| `limit` | number | `50` | Optional number of lines to read |
894
895##### Glob
896
897Finds files matching a glob pattern.
898
899| Field | Type | Example | Description |
900| :-------- | :----- | :--------------- | :--------------------------------------------------------------------- |
901| `pattern` | string | `"**/*.ts"` | Glob pattern to match files against |
902| `path` | string | `"/path/to/dir"` | Optional directory to search in. Defaults to current working directory |
903
904##### Grep
905
906Searches file contents with regular expressions.
907
908| Field | Type | Example | Description |
909| :------------ | :------ | :--------------- | :------------------------------------------------------------------------------------ |
910| `pattern` | string | `"TODO.*fix"` | Regular expression pattern to search for |
911| `path` | string | `"/path/to/dir"` | Optional file or directory to search in |
912| `glob` | string | `"*.ts"` | Optional glob pattern to filter files |
913| `output_mode` | string | `"content"` | `"content"`, `"files_with_matches"`, or `"count"`. Defaults to `"files_with_matches"` |
914| `-i` | boolean | `true` | Case insensitive search |
915| `multiline` | boolean | `false` | Enable multiline matching |
916
917##### WebFetch
918
919Fetches and processes web content.
920
921| Field | Type | Example | Description |
922| :------- | :----- | :---------------------------- | :----------------------------------- |
923| `url` | string | `"https://example.com/api"` | URL to fetch content from |
924| `prompt` | string | `"Extract the API endpoints"` | Prompt to run on the fetched content |
925
926##### WebSearch
927
928Searches the web.
929
930| Field | Type | Example | Description |
931| :---------------- | :----- | :----------------------------- | :------------------------------------------------ |
932| `query` | string | `"react hooks best practices"` | Search query |
933| `allowed_domains` | array | `["docs.example.com"]` | Optional: only include results from these domains |
934| `blocked_domains` | array | `["spam.example.com"]` | Optional: exclude results from these domains |
935
936##### Agent
937
938Spawns a [subagent](/en/sub-agents).
939
940| Field | Type | Example | Description |
941| :-------------- | :----- | :------------------------- | :------------------------------------------- |
942| `prompt` | string | `"Find all API endpoints"` | The task for the agent to perform |
943| `description` | string | `"Find API endpoints"` | Short description of the task |
944| `subagent_type` | string | `"Explore"` | Type of specialized agent to use |
945| `model` | string | `"sonnet"` | Optional model alias to override the default |
946
947##### AskUserQuestion
948
949Asks the user one to four multiple-choice questions.
950
951| Field | Type | Example | Description |
952| :---------- | :----- | :----------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
953| `questions` | array | `[{"question": "Which framework?", "header": "Framework", "options": [{"label": "React"}], "multiSelect": false}]` | Questions to present, each with a `question` string, short `header`, `options` array, and optional `multiSelect` flag |
954| `answers` | object | `{"Which framework?": "React"}` | Optional. Maps question text to the selected option label. Multi-select answers join labels with commas. Claude does not set this field; supply it via `updatedInput` to answer programmatically |
955
956#### PreToolUse decision control
957
958`PreToolUse` hooks can control whether a tool call proceeds. Unlike other hooks that use a top-level `decision` field, PreToolUse returns its decision inside a `hookSpecificOutput` object. This gives it richer control: three outcomes (allow, deny, or ask) plus the ability to modify tool input before execution.
959
960| Field | Description |
961| :------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
962| `permissionDecision` | `"allow"` skips the permission prompt. `"deny"` prevents the tool call. `"ask"` prompts the user to confirm. [Deny and ask rules](/en/permissions#manage-permissions) still apply when a hook returns `"allow"` |
963| `permissionDecisionReason` | For `"allow"` and `"ask"`, shown to the user but not Claude. For `"deny"`, shown to Claude |
964| `updatedInput` | Modifies the tool's input parameters before execution. Replaces the entire input object, so include unchanged fields alongside modified ones. Combine with `"allow"` to auto-approve, or `"ask"` to show the modified input to the user |
965| `additionalContext` | String added to Claude's context before the tool executes |
966
967When a hook returns `"ask"`, the permission prompt displayed to the user includes a label identifying where the hook came from: for example, `[User]`, `[Project]`, `[Plugin]`, or `[Local]`. This helps users understand which configuration source is requesting confirmation.
968
969```json theme={null}
970{
971 "hookSpecificOutput": {
972 "hookEventName": "PreToolUse",
973 "permissionDecision": "allow",
974 "permissionDecisionReason": "My reason here",
975 "updatedInput": {
976 "field_to_modify": "new value"
572 },977 },
573 "tool_use_id": "toolu_01ABC123..."978 "additionalContext": "Current environment: production. Proceed with caution."
979 }
574}980}
575```981```
576 982
577| Field | Type | Description |983`AskUserQuestion` and `ExitPlanMode` require user interaction and normally block in [non-interactive mode](/en/headless) with the `-p` flag. Returning `permissionDecision: "allow"` together with `updatedInput` satisfies that requirement: the hook reads the tool's input from stdin, collects the answer through your own UI, and returns it in `updatedInput` so the tool runs without prompting. Returning `"allow"` alone is not sufficient for these tools. For `AskUserQuestion`, echo back the original `questions` array and add an [`answers`](#askuserquestion) object mapping each question's text to the chosen answer.
578| :------------ | :------ | :-------------------------------------------------- |984
579| `file_path` | string | Absolute path to the file to edit |985<Note>
580| `old_string` | string | Text to find and replace |986 PreToolUse previously used top-level `decision` and `reason` fields, but these are deprecated for this event. Use `hookSpecificOutput.permissionDecision` and `hookSpecificOutput.permissionDecisionReason` instead. The deprecated values `"approve"` and `"block"` map to `"allow"` and `"deny"` respectively. Other events like PostToolUse and Stop continue to use top-level `decision` and `reason` as their current format.
581| `new_string` | string | Replacement text |987</Note>
582| `replace_all` | boolean | Whether to replace all occurrences (default: false) |988
989### PermissionRequest
990
991Runs when the user is shown a permission dialog.
992Use [PermissionRequest decision control](#permissionrequest-decision-control) to allow or deny on behalf of the user.
993
994Matches on tool name, same values as PreToolUse.
583 995
584#### Read tool996#### PermissionRequest input
997
998PermissionRequest hooks receive `tool_name` and `tool_input` fields like PreToolUse hooks, but without `tool_use_id`. An optional `permission_suggestions` array contains the "always allow" options the user would normally see in the permission dialog. The difference is when the hook fires: PermissionRequest hooks run when a permission dialog is about to be shown to the user, while PreToolUse hooks run before tool execution regardless of permission status.
585 999
586```json theme={null}1000```json theme={null}
587{1001{
589 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1003 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
590 "cwd": "/Users/...",1004 "cwd": "/Users/...",
591 "permission_mode": "default",1005 "permission_mode": "default",
592 "hook_event_name": "PreToolUse",1006 "hook_event_name": "PermissionRequest",
593 "tool_name": "Read",1007 "tool_name": "Bash",
594 "tool_input": {1008 "tool_input": {
595 "file_path": "/path/to/file.txt"1009 "command": "rm -rf node_modules",
1010 "description": "Remove node_modules directory"
596 },1011 },
597 "tool_use_id": "toolu_01ABC123..."1012 "permission_suggestions": [
1013 {
1014 "type": "addRules",
1015 "rules": [{ "toolName": "Bash", "ruleContent": "rm -rf node_modules" }],
1016 "behavior": "allow",
1017 "destination": "localSettings"
1018 }
1019 ]
1020}
1021```
1022
1023#### PermissionRequest decision control
1024
1025`PermissionRequest` hooks can allow or deny permission requests. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return a `decision` object with these event-specific fields:
1026
1027| Field | Description |
1028| :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
1029| `behavior` | `"allow"` grants the permission, `"deny"` denies it |
1030| `updatedInput` | For `"allow"` only: modifies the tool's input parameters before execution. Replaces the entire input object, so include unchanged fields alongside modified ones |
1031| `updatedPermissions` | For `"allow"` only: array of [permission update entries](#permission-update-entries) to apply, such as adding an allow rule or changing the session permission mode |
1032| `message` | For `"deny"` only: tells Claude why the permission was denied |
1033| `interrupt` | For `"deny"` only: if `true`, stops Claude |
1034
1035```json theme={null}
1036{
1037 "hookSpecificOutput": {
1038 "hookEventName": "PermissionRequest",
1039 "decision": {
1040 "behavior": "allow",
1041 "updatedInput": {
1042 "command": "npm run lint"
1043 }
1044 }
1045 }
598}1046}
599```1047```
600 1048
601| Field | Type | Description |1049#### Permission update entries
602| :---------- | :----- | :----------------------------------------- |1050
603| `file_path` | string | Absolute path to the file to read |1051The `updatedPermissions` output field and the [`permission_suggestions` input field](#permissionrequest-input) both use the same array of entry objects. Each entry has a `type` that determines its other fields, and a `destination` that controls where the change is written.
604| `offset` | number | Optional line number to start reading from |1052
605| `limit` | number | Optional number of lines to read |1053| `type` | Fields | Effect |
1054| :------------------ | :--------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1055| `addRules` | `rules`, `behavior`, `destination` | Adds permission rules. `rules` is an array of `{toolName, ruleContent?}` objects. Omit `ruleContent` to match the whole tool. `behavior` is `"allow"`, `"deny"`, or `"ask"` |
1056| `replaceRules` | `rules`, `behavior`, `destination` | Replaces all rules of the given `behavior` at the `destination` with the provided `rules` |
1057| `removeRules` | `rules`, `behavior`, `destination` | Removes matching rules of the given `behavior` |
1058| `setMode` | `mode`, `destination` | Changes the permission mode. Valid modes are `default`, `acceptEdits`, `dontAsk`, `bypassPermissions`, and `plan` |
1059| `addDirectories` | `directories`, `destination` | Adds working directories. `directories` is an array of path strings |
1060| `removeDirectories` | `directories`, `destination` | Removes working directories |
1061
1062The `destination` field on every entry determines whether the change stays in memory or persists to a settings file.
606 1063
607### PostToolUse Input1064| `destination` | Writes to |
1065| :---------------- | :---------------------------------------------- |
1066| `session` | in-memory only, discarded when the session ends |
1067| `localSettings` | `.claude/settings.local.json` |
1068| `projectSettings` | `.claude/settings.json` |
1069| `userSettings` | `~/.claude/settings.json` |
608 1070
609The exact schema for `tool_input` and `tool_response` depends on the tool.1071A hook can echo one of the `permission_suggestions` it received as its own `updatedPermissions` output, which is equivalent to the user selecting that "always allow" option in the dialog.
1072
1073### PostToolUse
1074
1075Runs immediately after a tool completes successfully.
1076
1077Matches on tool name, same values as PreToolUse.
1078
1079#### PostToolUse input
1080
1081`PostToolUse` hooks fire after a tool has already executed successfully. The input includes both `tool_input`, the arguments sent to the tool, and `tool_response`, the result it returned. The exact schema for both depends on the tool.
610 1082
611```json theme={null}1083```json theme={null}
612{1084{
628}1100}
629```1101```
630 1102
631### Notification Input1103#### PostToolUse decision control
1104
1105`PostToolUse` hooks can provide feedback to Claude after tool execution. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return these event-specific fields:
1106
1107| Field | Description |
1108| :--------------------- | :----------------------------------------------------------------------------------------- |
1109| `decision` | `"block"` prompts Claude with the `reason`. Omit to allow the action to proceed |
1110| `reason` | Explanation shown to Claude when `decision` is `"block"` |
1111| `additionalContext` | Additional context for Claude to consider |
1112| `updatedMCPToolOutput` | For [MCP tools](#match-mcp-tools) only: replaces the tool's output with the provided value |
1113
1114```json theme={null}
1115{
1116 "decision": "block",
1117 "reason": "Explanation for decision",
1118 "hookSpecificOutput": {
1119 "hookEventName": "PostToolUse",
1120 "additionalContext": "Additional information for Claude"
1121 }
1122}
1123```
1124
1125### PostToolUseFailure
1126
1127Runs when a tool execution fails. This event fires for tool calls that throw errors or return failure results. Use this to log failures, send alerts, or provide corrective feedback to Claude.
1128
1129Matches on tool name, same values as PreToolUse.
1130
1131#### PostToolUseFailure input
1132
1133PostToolUseFailure hooks receive the same `tool_name` and `tool_input` fields as PostToolUse, along with error information as top-level fields:
1134
1135```json theme={null}
1136{
1137 "session_id": "abc123",
1138 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1139 "cwd": "/Users/...",
1140 "permission_mode": "default",
1141 "hook_event_name": "PostToolUseFailure",
1142 "tool_name": "Bash",
1143 "tool_input": {
1144 "command": "npm test",
1145 "description": "Run test suite"
1146 },
1147 "tool_use_id": "toolu_01ABC123...",
1148 "error": "Command exited with non-zero status code 1",
1149 "is_interrupt": false
1150}
1151```
1152
1153| Field | Description |
1154| :------------- | :------------------------------------------------------------------------------ |
1155| `error` | String describing what went wrong |
1156| `is_interrupt` | Optional boolean indicating whether the failure was caused by user interruption |
1157
1158#### PostToolUseFailure decision control
1159
1160`PostToolUseFailure` hooks can provide context to Claude after a tool failure. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return these event-specific fields:
1161
1162| Field | Description |
1163| :------------------ | :------------------------------------------------------------ |
1164| `additionalContext` | Additional context for Claude to consider alongside the error |
1165
1166```json theme={null}
1167{
1168 "hookSpecificOutput": {
1169 "hookEventName": "PostToolUseFailure",
1170 "additionalContext": "Additional information about the failure for Claude"
1171 }
1172}
1173```
1174
1175### Notification
1176
1177Runs when Claude Code sends notifications. Matches on notification type: `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog`. Omit the matcher to run hooks for all notification types.
1178
1179Use separate matchers to run different handlers depending on the notification type. This configuration triggers a permission-specific alert script when Claude needs permission approval and a different notification when Claude has been idle:
1180
1181```json theme={null}
1182{
1183 "hooks": {
1184 "Notification": [
1185 {
1186 "matcher": "permission_prompt",
1187 "hooks": [
1188 {
1189 "type": "command",
1190 "command": "/path/to/permission-alert.sh"
1191 }
1192 ]
1193 },
1194 {
1195 "matcher": "idle_prompt",
1196 "hooks": [
1197 {
1198 "type": "command",
1199 "command": "/path/to/idle-notification.sh"
1200 }
1201 ]
1202 }
1203 ]
1204 }
1205}
1206```
1207
1208#### Notification input
1209
1210In addition to the [common input fields](#common-input-fields), Notification hooks receive `message` with the notification text, an optional `title`, and `notification_type` indicating which type fired.
1211
1212```json theme={null}
1213{
1214 "session_id": "abc123",
1215 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1216 "cwd": "/Users/...",
1217 "hook_event_name": "Notification",
1218 "message": "Claude needs your permission to use Bash",
1219 "title": "Permission needed",
1220 "notification_type": "permission_prompt"
1221}
1222```
1223
1224Notification hooks cannot block or modify notifications. In addition to the [JSON output fields](#json-output) available to all hooks, you can return `additionalContext` to add context to the conversation:
1225
1226| Field | Description |
1227| :------------------ | :------------------------------- |
1228| `additionalContext` | String added to Claude's context |
1229
1230### SubagentStart
1231
1232Runs when a Claude Code subagent is spawned via the Agent tool. Supports matchers to filter by agent type name (built-in agents like `Bash`, `Explore`, `Plan`, or custom agent names from `.claude/agents/`).
1233
1234#### SubagentStart input
1235
1236In addition to the [common input fields](#common-input-fields), SubagentStart hooks receive `agent_id` with the unique identifier for the subagent and `agent_type` with the agent name (built-in agents like `"Bash"`, `"Explore"`, `"Plan"`, or custom agent names).
1237
1238```json theme={null}
1239{
1240 "session_id": "abc123",
1241 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1242 "cwd": "/Users/...",
1243 "hook_event_name": "SubagentStart",
1244 "agent_id": "agent-abc123",
1245 "agent_type": "Explore"
1246}
1247```
1248
1249SubagentStart hooks cannot block subagent creation, but they can inject context into the subagent. In addition to the [JSON output fields](#json-output) available to all hooks, you can return:
1250
1251| Field | Description |
1252| :------------------ | :------------------------------------- |
1253| `additionalContext` | String added to the subagent's context |
1254
1255```json theme={null}
1256{
1257 "hookSpecificOutput": {
1258 "hookEventName": "SubagentStart",
1259 "additionalContext": "Follow security guidelines for this task"
1260 }
1261}
1262```
1263
1264### SubagentStop
1265
1266Runs when a Claude Code subagent has finished responding. Matches on agent type, same values as SubagentStart.
1267
1268#### SubagentStop input
1269
1270In addition to the [common input fields](#common-input-fields), SubagentStop hooks receive `stop_hook_active`, `agent_id`, `agent_type`, `agent_transcript_path`, and `last_assistant_message`. The `agent_type` field is the value used for matcher filtering. The `transcript_path` is the main session's transcript, while `agent_transcript_path` is the subagent's own transcript stored in a nested `subagents/` folder. The `last_assistant_message` field contains the text content of the subagent's final response, so hooks can access it without parsing the transcript file.
1271
1272```json theme={null}
1273{
1274 "session_id": "abc123",
1275 "transcript_path": "~/.claude/projects/.../abc123.jsonl",
1276 "cwd": "/Users/...",
1277 "permission_mode": "default",
1278 "hook_event_name": "SubagentStop",
1279 "stop_hook_active": false,
1280 "agent_id": "def456",
1281 "agent_type": "Explore",
1282 "agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl",
1283 "last_assistant_message": "Analysis complete. Found 3 potential issues..."
1284}
1285```
1286
1287SubagentStop hooks use the same decision control format as [Stop hooks](#stop-decision-control).
1288
1289### TaskCreated
1290
1291Runs when a task is being created via the `TaskCreate` tool. Use this to enforce naming conventions, require task descriptions, or prevent certain tasks from being created.
1292
1293When a `TaskCreated` hook exits with code 2, the task is not created and the stderr message is fed back to the model as feedback. To stop the teammate entirely instead of re-running it, return JSON with `{"continue": false, "stopReason": "..."}`. TaskCreated hooks do not support matchers and fire on every occurrence.
1294
1295#### TaskCreated input
1296
1297In addition to the [common input fields](#common-input-fields), TaskCreated hooks receive `task_id`, `task_subject`, and optionally `task_description`, `teammate_name`, and `team_name`.
1298
1299```json theme={null}
1300{
1301 "session_id": "abc123",
1302 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1303 "cwd": "/Users/...",
1304 "permission_mode": "default",
1305 "hook_event_name": "TaskCreated",
1306 "task_id": "task-001",
1307 "task_subject": "Implement user authentication",
1308 "task_description": "Add login and signup endpoints",
1309 "teammate_name": "implementer",
1310 "team_name": "my-project"
1311}
1312```
1313
1314| Field | Description |
1315| :----------------- | :---------------------------------------------------- |
1316| `task_id` | Identifier of the task being created |
1317| `task_subject` | Title of the task |
1318| `task_description` | Detailed description of the task. May be absent |
1319| `teammate_name` | Name of the teammate creating the task. May be absent |
1320| `team_name` | Name of the team. May be absent |
1321
1322#### TaskCreated decision control
1323
1324TaskCreated hooks support two ways to control task creation:
1325
1326* **Exit code 2**: the task is not created and the stderr message is fed back to the model as feedback.
1327* **JSON `{"continue": false, "stopReason": "..."}`**: stops the teammate entirely, matching `Stop` hook behavior. The `stopReason` is shown to the user.
1328
1329This example blocks tasks whose subjects don't follow the required format:
1330
1331```bash theme={null}
1332#!/bin/bash
1333INPUT=$(cat)
1334TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')
1335
1336if [[ ! "$TASK_SUBJECT" =~ ^\[TICKET-[0-9]+\] ]]; then
1337 echo "Task subject must start with a ticket number, e.g. '[TICKET-123] Add feature'" >&2
1338 exit 2
1339fi
1340
1341exit 0
1342```
1343
1344### TaskCompleted
1345
1346Runs when a task is being marked as completed. This fires in two situations: when any agent explicitly marks a task as completed through the TaskUpdate tool, or when an [agent team](/en/agent-teams) teammate finishes its turn with in-progress tasks. Use this to enforce completion criteria like passing tests or lint checks before a task can close.
1347
1348When a `TaskCompleted` hook exits with code 2, the task is not marked as completed and the stderr message is fed back to the model as feedback. To stop the teammate entirely instead of re-running it, return JSON with `{"continue": false, "stopReason": "..."}`. TaskCompleted hooks do not support matchers and fire on every occurrence.
1349
1350#### TaskCompleted input
1351
1352In addition to the [common input fields](#common-input-fields), TaskCompleted hooks receive `task_id`, `task_subject`, and optionally `task_description`, `teammate_name`, and `team_name`.
1353
1354```json theme={null}
1355{
1356 "session_id": "abc123",
1357 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1358 "cwd": "/Users/...",
1359 "permission_mode": "default",
1360 "hook_event_name": "TaskCompleted",
1361 "task_id": "task-001",
1362 "task_subject": "Implement user authentication",
1363 "task_description": "Add login and signup endpoints",
1364 "teammate_name": "implementer",
1365 "team_name": "my-project"
1366}
1367```
1368
1369| Field | Description |
1370| :----------------- | :------------------------------------------------------ |
1371| `task_id` | Identifier of the task being completed |
1372| `task_subject` | Title of the task |
1373| `task_description` | Detailed description of the task. May be absent |
1374| `teammate_name` | Name of the teammate completing the task. May be absent |
1375| `team_name` | Name of the team. May be absent |
1376
1377#### TaskCompleted decision control
1378
1379TaskCompleted hooks support two ways to control task completion:
1380
1381* **Exit code 2**: the task is not marked as completed and the stderr message is fed back to the model as feedback.
1382* **JSON `{"continue": false, "stopReason": "..."}`**: stops the teammate entirely, matching `Stop` hook behavior. The `stopReason` is shown to the user.
1383
1384This example runs tests and blocks task completion if they fail:
1385
1386```bash theme={null}
1387#!/bin/bash
1388INPUT=$(cat)
1389TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')
1390
1391# Run the test suite
1392if ! npm test 2>&1; then
1393 echo "Tests not passing. Fix failing tests before completing: $TASK_SUBJECT" >&2
1394 exit 2
1395fi
1396
1397exit 0
1398```
1399
1400### Stop
1401
1402Runs when the main Claude Code agent has finished responding. Does not run if
1403the stoppage occurred due to a user interrupt. API errors fire
1404[StopFailure](#stopfailure) instead.
1405
1406#### Stop input
1407
1408In addition to the [common input fields](#common-input-fields), Stop hooks receive `stop_hook_active` and `last_assistant_message`. The `stop_hook_active` field is `true` when Claude Code is already continuing as a result of a stop hook. Check this value or process the transcript to prevent Claude Code from running indefinitely. The `last_assistant_message` field contains the text content of Claude's final response, so hooks can access it without parsing the transcript file.
1409
1410```json theme={null}
1411{
1412 "session_id": "abc123",
1413 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1414 "cwd": "/Users/...",
1415 "permission_mode": "default",
1416 "hook_event_name": "Stop",
1417 "stop_hook_active": true,
1418 "last_assistant_message": "I've completed the refactoring. Here's a summary..."
1419}
1420```
1421
1422#### Stop decision control
1423
1424`Stop` and `SubagentStop` hooks can control whether Claude continues. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return these event-specific fields:
1425
1426| Field | Description |
1427| :--------- | :------------------------------------------------------------------------- |
1428| `decision` | `"block"` prevents Claude from stopping. Omit to allow Claude to stop |
1429| `reason` | Required when `decision` is `"block"`. Tells Claude why it should continue |
1430
1431```json theme={null}
1432{
1433 "decision": "block",
1434 "reason": "Must be provided when Claude is blocked from stopping"
1435}
1436```
1437
1438### StopFailure
1439
1440Runs instead of [Stop](#stop) when the turn ends due to an API error. Output and exit code are ignored. Use this to log failures, send alerts, or take recovery actions when Claude cannot complete a response due to rate limits, authentication problems, or other API errors.
1441
1442#### StopFailure input
1443
1444In addition to the [common input fields](#common-input-fields), StopFailure hooks receive `error`, optional `error_details`, and optional `last_assistant_message`. The `error` field identifies the error type and is used for matcher filtering.
1445
1446| Field | Description |
1447| :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1448| `error` | Error type: `rate_limit`, `authentication_failed`, `billing_error`, `invalid_request`, `server_error`, `max_output_tokens`, or `unknown` |
1449| `error_details` | Additional details about the error, when available |
1450| `last_assistant_message` | The rendered error text shown in the conversation. Unlike `Stop` and `SubagentStop`, where this field holds Claude's conversational output, for `StopFailure` it contains the API error string itself, such as `"API Error: Rate limit reached"` |
1451
1452```json theme={null}
1453{
1454 "session_id": "abc123",
1455 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1456 "cwd": "/Users/...",
1457 "hook_event_name": "StopFailure",
1458 "error": "rate_limit",
1459 "error_details": "429 Too Many Requests",
1460 "last_assistant_message": "API Error: Rate limit reached"
1461}
1462```
1463
1464StopFailure hooks have no decision control. They run for notification and logging purposes only.
1465
1466### TeammateIdle
1467
1468Runs when an [agent team](/en/agent-teams) teammate is about to go idle after finishing its turn. Use this to enforce quality gates before a teammate stops working, such as requiring passing lint checks or verifying that output files exist.
1469
1470When a `TeammateIdle` hook exits with code 2, the teammate receives the stderr message as feedback and continues working instead of going idle. To stop the teammate entirely instead of re-running it, return JSON with `{"continue": false, "stopReason": "..."}`. TeammateIdle hooks do not support matchers and fire on every occurrence.
1471
1472#### TeammateIdle input
1473
1474In addition to the [common input fields](#common-input-fields), TeammateIdle hooks receive `teammate_name` and `team_name`.
1475
1476```json theme={null}
1477{
1478 "session_id": "abc123",
1479 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1480 "cwd": "/Users/...",
1481 "permission_mode": "default",
1482 "hook_event_name": "TeammateIdle",
1483 "teammate_name": "researcher",
1484 "team_name": "my-project"
1485}
1486```
1487
1488| Field | Description |
1489| :-------------- | :-------------------------------------------- |
1490| `teammate_name` | Name of the teammate that is about to go idle |
1491| `team_name` | Name of the team |
1492
1493#### TeammateIdle decision control
1494
1495TeammateIdle hooks support two ways to control teammate behavior:
1496
1497* **Exit code 2**: the teammate receives the stderr message as feedback and continues working instead of going idle.
1498* **JSON `{"continue": false, "stopReason": "..."}`**: stops the teammate entirely, matching `Stop` hook behavior. The `stopReason` is shown to the user.
1499
1500This example checks that a build artifact exists before allowing a teammate to go idle:
1501
1502```bash theme={null}
1503#!/bin/bash
1504
1505if [ ! -f "./dist/output.js" ]; then
1506 echo "Build artifact missing. Run the build before stopping." >&2
1507 exit 2
1508fi
1509
1510exit 0
1511```
1512
1513### ConfigChange
1514
1515Runs when a configuration file changes during a session. Use this to audit settings changes, enforce security policies, or block unauthorized modifications to configuration files.
1516
1517ConfigChange hooks fire for changes to settings files, managed policy settings, and skill files. The `source` field in the input tells you which type of configuration changed, and the optional `file_path` field provides the path to the changed file.
1518
1519The matcher filters on the configuration source:
1520
1521| Matcher | When it fires |
1522| :----------------- | :---------------------------------------- |
1523| `user_settings` | `~/.claude/settings.json` changes |
1524| `project_settings` | `.claude/settings.json` changes |
1525| `local_settings` | `.claude/settings.local.json` changes |
1526| `policy_settings` | Managed policy settings change |
1527| `skills` | A skill file in `.claude/skills/` changes |
1528
1529This example logs all configuration changes for security auditing:
1530
1531```json theme={null}
1532{
1533 "hooks": {
1534 "ConfigChange": [
1535 {
1536 "hooks": [
1537 {
1538 "type": "command",
1539 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/audit-config-change.sh"
1540 }
1541 ]
1542 }
1543 ]
1544 }
1545}
1546```
1547
1548#### ConfigChange input
1549
1550In addition to the [common input fields](#common-input-fields), ConfigChange hooks receive `source` and optionally `file_path`. The `source` field indicates which configuration type changed, and `file_path` provides the path to the specific file that was modified.
1551
1552```json theme={null}
1553{
1554 "session_id": "abc123",
1555 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1556 "cwd": "/Users/...",
1557 "hook_event_name": "ConfigChange",
1558 "source": "project_settings",
1559 "file_path": "/Users/.../my-project/.claude/settings.json"
1560}
1561```
1562
1563#### ConfigChange decision control
1564
1565ConfigChange hooks can block configuration changes from taking effect. Use exit code 2 or a JSON `decision` to prevent the change. When blocked, the new settings are not applied to the running session.
1566
1567| Field | Description |
1568| :--------- | :--------------------------------------------------------------------------------------- |
1569| `decision` | `"block"` prevents the configuration change from being applied. Omit to allow the change |
1570| `reason` | Explanation shown to the user when `decision` is `"block"` |
1571
1572```json theme={null}
1573{
1574 "decision": "block",
1575 "reason": "Configuration changes to project settings require admin approval"
1576}
1577```
1578
1579`policy_settings` changes cannot be blocked. Hooks still fire for `policy_settings` sources, so you can use them for audit logging, but any blocking decision is ignored. This ensures enterprise-managed settings always take effect.
1580
1581### CwdChanged
1582
1583Runs when the working directory changes during a session, for example when Claude executes a `cd` command. Use this to react to directory changes: reload environment variables, activate project-specific toolchains, or run setup scripts automatically. Pairs with [FileChanged](#filechanged) for tools like [direnv](https://direnv.net/) that manage per-directory environment.
1584
1585CwdChanged hooks have access to `CLAUDE_ENV_FILE`. Variables written to that file persist into subsequent Bash commands for the session, just as in [SessionStart hooks](#persist-environment-variables). Only `type: "command"` hooks are supported.
1586
1587CwdChanged does not support matchers and fires on every directory change.
1588
1589#### CwdChanged input
1590
1591In addition to the [common input fields](#common-input-fields), CwdChanged hooks receive `old_cwd` and `new_cwd`.
1592
1593```json theme={null}
1594{
1595 "session_id": "abc123",
1596 "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
1597 "cwd": "/Users/my-project/src",
1598 "hook_event_name": "CwdChanged",
1599 "old_cwd": "/Users/my-project",
1600 "new_cwd": "/Users/my-project/src"
1601}
1602```
1603
1604#### CwdChanged output
1605
1606In addition to the [JSON output fields](#json-output) available to all hooks, CwdChanged hooks can return `watchPaths` to dynamically set which file paths [FileChanged](#filechanged) watches:
1607
1608| Field | Description |
1609| :----------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
1610| `watchPaths` | Array of absolute paths. Replaces the current dynamic watch list (paths from your `matcher` configuration are always watched). Returning an empty array clears the dynamic list, which is typical when entering a new directory |
1611
1612CwdChanged hooks have no decision control. They cannot block the directory change.
1613
1614### FileChanged
1615
1616Runs when a watched file changes on disk. The `matcher` field in your hook configuration controls which filenames to watch: it is a pipe-separated list of basenames (filenames without directory paths, for example `".envrc|.env"`). The same `matcher` value is also used to filter which hooks run when a file changes, matching against the basename of the changed file. Useful for reloading environment variables when project configuration files are modified.
1617
1618FileChanged hooks have access to `CLAUDE_ENV_FILE`. Variables written to that file persist into subsequent Bash commands for the session, just as in [SessionStart hooks](#persist-environment-variables). Only `type: "command"` hooks are supported.
1619
1620#### FileChanged input
1621
1622In addition to the [common input fields](#common-input-fields), FileChanged hooks receive `file_path` and `event`.
1623
1624| Field | Description |
1625| :---------- | :---------------------------------------------------------------------------------------------- |
1626| `file_path` | Absolute path to the file that changed |
1627| `event` | What happened: `"change"` (file modified), `"add"` (file created), or `"unlink"` (file deleted) |
1628
1629```json theme={null}
1630{
1631 "session_id": "abc123",
1632 "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
1633 "cwd": "/Users/my-project",
1634 "hook_event_name": "FileChanged",
1635 "file_path": "/Users/my-project/.envrc",
1636 "event": "change"
1637}
1638```
1639
1640#### FileChanged output
1641
1642In addition to the [JSON output fields](#json-output) available to all hooks, FileChanged hooks can return `watchPaths` to dynamically update which file paths are watched:
1643
1644| Field | Description |
1645| :----------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1646| `watchPaths` | Array of absolute paths. Replaces the current dynamic watch list (paths from your `matcher` configuration are always watched). Use this when your hook script discovers additional files to watch based on the changed file |
1647
1648FileChanged hooks have no decision control. They cannot block the file change from occurring.
1649
1650### WorktreeCreate
1651
1652When you run `claude --worktree` or a [subagent uses `isolation: "worktree"`](/en/sub-agents#choose-the-subagent-scope), Claude Code creates an isolated working copy using `git worktree`. If you configure a WorktreeCreate hook, it replaces the default git behavior, letting you use a different version control system like SVN, Perforce, or Mercurial.
1653
1654Because the hook replaces the default behavior entirely, [`.worktreeinclude`](/en/common-workflows#copy-gitignored-files-to-worktrees) is not processed. If you need to copy local configuration files like `.env` into the new worktree, do it inside your hook script.
1655
1656The hook must return the absolute path to the created worktree directory. Claude Code uses this path as the working directory for the isolated session. Command hooks print it on stdout; HTTP hooks return it via `hookSpecificOutput.worktreePath`.
1657
1658This example creates an SVN working copy and prints the path for Claude Code to use. Replace the repository URL with your own:
1659
1660```json theme={null}
1661{
1662 "hooks": {
1663 "WorktreeCreate": [
1664 {
1665 "hooks": [
1666 {
1667 "type": "command",
1668 "command": "bash -c 'NAME=$(jq -r .name); DIR=\"$HOME/.claude/worktrees/$NAME\"; svn checkout https://svn.example.com/repo/trunk \"$DIR\" >&2 && echo \"$DIR\"'"
1669 }
1670 ]
1671 }
1672 ]
1673 }
1674}
1675```
1676
1677The hook reads the worktree `name` from the JSON input on stdin, checks out a fresh copy into a new directory, and prints the directory path. The `echo` on the last line is what Claude Code reads as the worktree path. Redirect any other output to stderr so it doesn't interfere with the path.
1678
1679#### WorktreeCreate input
1680
1681In addition to the [common input fields](#common-input-fields), WorktreeCreate hooks receive the `name` field. This is a slug identifier for the new worktree, either specified by the user or auto-generated (for example, `bold-oak-a3f2`).
1682
1683```json theme={null}
1684{
1685 "session_id": "abc123",
1686 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1687 "cwd": "/Users/...",
1688 "hook_event_name": "WorktreeCreate",
1689 "name": "feature-auth"
1690}
1691```
1692
1693#### WorktreeCreate output
1694
1695WorktreeCreate hooks do not use the standard allow/block decision model. Instead, the hook's success or failure determines the outcome. The hook must return the absolute path to the created worktree directory:
1696
1697* **Command hooks** (`type: "command"`): print the path on stdout.
1698* **HTTP hooks** (`type: "http"`): return `{ "hookSpecificOutput": { "hookEventName": "WorktreeCreate", "worktreePath": "/absolute/path" } }` in the response body.
1699
1700If the hook fails or produces no path, worktree creation fails with an error.
1701
1702### WorktreeRemove
1703
1704The cleanup counterpart to [WorktreeCreate](#worktreecreate). This hook fires when a worktree is being removed, either when you exit a `--worktree` session and choose to remove it, or when a subagent with `isolation: "worktree"` finishes. For git-based worktrees, Claude handles cleanup automatically with `git worktree remove`. If you configured a WorktreeCreate hook for a non-git version control system, pair it with a WorktreeRemove hook to handle cleanup. Without one, the worktree directory is left on disk.
1705
1706Claude Code passes the path returned by WorktreeCreate as `worktree_path` in the hook input. This example reads that path and removes the directory:
632 1707
633```json theme={null}1708```json theme={null}
634{1709{
635 "session_id": "abc123",1710 "hooks": {
636 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1711 "WorktreeRemove": [
637 "cwd": "/Users/...",1712 {
638 "permission_mode": "default",1713 "hooks": [
639 "hook_event_name": "Notification",1714 {
640 "message": "Claude needs your permission to use Bash",1715 "type": "command",
641 "notification_type": "permission_prompt"1716 "command": "bash -c 'jq -r .worktree_path | xargs rm -rf'"
1717 }
1718 ]
1719 }
1720 ]
1721 }
642}1722}
643```1723```
644 1724
645### UserPromptSubmit Input1725#### WorktreeRemove input
1726
1727In addition to the [common input fields](#common-input-fields), WorktreeRemove hooks receive the `worktree_path` field, which is the absolute path to the worktree being removed.
646 1728
647```json theme={null}1729```json theme={null}
648{1730{
649 "session_id": "abc123",1731 "session_id": "abc123",
650 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1732 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
651 "cwd": "/Users/...",1733 "cwd": "/Users/...",
652 "permission_mode": "default",1734 "hook_event_name": "WorktreeRemove",
653 "hook_event_name": "UserPromptSubmit",1735 "worktree_path": "/Users/.../my-project/.claude/worktrees/feature-auth"
654 "prompt": "Write a function to calculate the factorial of a number"
655}1736}
656```1737```
657 1738
658### Stop and SubagentStop Input1739WorktreeRemove hooks have no decision control. They cannot block worktree removal but can perform cleanup tasks like removing version control state or archiving changes. Hook failures are logged in debug mode only.
659 1740
660`stop_hook_active` is true when Claude Code is already continuing as a result of1741### PreCompact
661a stop hook. Check this value or process the transcript to prevent Claude Code
662from running indefinitely.
663 1742
664```json theme={null}1743Runs before Claude Code is about to run a compact operation.
665{1744
666 "session_id": "abc123",1745The matcher value indicates whether compaction was triggered manually or automatically:
667 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
668 "permission_mode": "default",
669 "hook_event_name": "Stop",
670 "stop_hook_active": true
671}
672```
673 1746
674### PreCompact Input1747| Matcher | When it fires |
1748| :------- | :------------------------------------------- |
1749| `manual` | `/compact` |
1750| `auto` | Auto-compact when the context window is full |
675 1751
676For `manual`, `custom_instructions` comes from what the user passes into1752#### PreCompact input
677`/compact`. For `auto`, `custom_instructions` is empty.1753
1754In addition to the [common input fields](#common-input-fields), PreCompact hooks receive `trigger` and `custom_instructions`. For `manual`, `custom_instructions` contains what the user passes into `/compact`. For `auto`, `custom_instructions` is empty.
678 1755
679```json theme={null}1756```json theme={null}
680{1757{
681 "session_id": "abc123",1758 "session_id": "abc123",
682 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1759 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
683 "permission_mode": "default",1760 "cwd": "/Users/...",
684 "hook_event_name": "PreCompact",1761 "hook_event_name": "PreCompact",
685 "trigger": "manual",1762 "trigger": "manual",
686 "custom_instructions": ""1763 "custom_instructions": ""
687}1764}
688```1765```
689 1766
690### SessionStart Input1767### PostCompact
1768
1769Runs after Claude Code completes a compact operation. Use this event to react to the new compacted state, for example to log the generated summary or update external state.
1770
1771The same matcher values apply as for `PreCompact`:
1772
1773| Matcher | When it fires |
1774| :------- | :------------------------------------------------- |
1775| `manual` | After `/compact` |
1776| `auto` | After auto-compact when the context window is full |
1777
1778#### PostCompact input
1779
1780In addition to the [common input fields](#common-input-fields), PostCompact hooks receive `trigger` and `compact_summary`. The `compact_summary` field contains the conversation summary generated by the compact operation.
691 1781
692```json theme={null}1782```json theme={null}
693{1783{
694 "session_id": "abc123",1784 "session_id": "abc123",
695 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1785 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
696 "permission_mode": "default",1786 "cwd": "/Users/...",
697 "hook_event_name": "SessionStart",1787 "hook_event_name": "PostCompact",
698 "source": "startup"1788 "trigger": "manual",
1789 "compact_summary": "Summary of the compacted conversation..."
699}1790}
700```1791```
701 1792
702### SessionEnd Input1793PostCompact hooks have no decision control. They cannot affect the compaction result but can perform follow-up tasks.
1794
1795### SessionEnd
1796
1797Runs when a Claude Code session ends. Useful for cleanup tasks, logging session
1798statistics, or saving session state. Supports matchers to filter by exit reason.
1799
1800The `reason` field in the hook input indicates why the session ended:
1801
1802| Reason | Description |
1803| :---------------------------- | :----------------------------------------- |
1804| `clear` | Session cleared with `/clear` command |
1805| `resume` | Session switched via interactive `/resume` |
1806| `logout` | User logged out |
1807| `prompt_input_exit` | User exited while prompt input was visible |
1808| `bypass_permissions_disabled` | Bypass permissions mode was disabled |
1809| `other` | Other exit reasons |
1810
1811#### SessionEnd input
1812
1813In addition to the [common input fields](#common-input-fields), SessionEnd hooks receive a `reason` field indicating why the session ended. See the [reason table](#sessionend) above for all values.
703 1814
704```json theme={null}1815```json theme={null}
705{1816{
706 "session_id": "abc123",1817 "session_id": "abc123",
707 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1818 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
708 "cwd": "/Users/...",1819 "cwd": "/Users/...",
709 "permission_mode": "default",
710 "hook_event_name": "SessionEnd",1820 "hook_event_name": "SessionEnd",
711 "reason": "exit"1821 "reason": "other"
712}1822}
713```1823```
714 1824
715## Hook Output1825SessionEnd hooks have no decision control. They cannot block session termination but can perform cleanup tasks.
716
717There are two mutually exclusive ways for hooks to return output back to Claude Code. The output
718communicates whether to block and any feedback that should be shown to Claude
719and the user.
720 1826
721### Simple: Exit Code1827SessionEnd hooks have a default timeout of 1.5 seconds. This applies to session exit, `/clear`, and switching sessions via interactive `/resume`. If your hooks need more time, set the `CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS` environment variable to a higher value in milliseconds. Any per-hook `timeout` setting is also capped by this value.
722
723Hooks communicate status through exit codes, stdout, and stderr:
724
725* **Exit code 0**: Success. `stdout` is shown to the user in verbose mode
726 (ctrl+o), except for `UserPromptSubmit` and `SessionStart`, where stdout is
727 added to the context. JSON output in `stdout` is parsed for structured control
728 (see [Advanced: JSON Output](#advanced-json-output)).
729* **Exit code 2**: Blocking error. Only `stderr` is used as the error message
730 and fed back to Claude. The format is `[command]: {stderr}`. JSON in `stdout`
731 is **not** processed for exit code 2. See per-hook-event behavior below.
732* **Other exit codes**: Non-blocking error. `stderr` is shown to the user in verbose mode (ctrl+o) with
733 format `Failed with non-blocking status code: {stderr}`. If `stderr` is empty,
734 it shows `No stderr output`. Execution continues.
735
736<Warning>
737 Reminder: Claude Code does not see stdout if the exit code is 0, except for
738 the `UserPromptSubmit` hook where stdout is injected as context.
739</Warning>
740 1828
741#### Exit Code 2 Behavior1829```bash theme={null}
1830CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=5000 claude
1831```
742 1832
743| Hook Event | Behavior |1833### Elicitation
744| ------------------- | ------------------------------------------------------------------ |
745| `PreToolUse` | Blocks the tool call, shows stderr to Claude |
746| `PermissionRequest` | Denies the permission, shows stderr to Claude |
747| `PostToolUse` | Shows stderr to Claude (tool already ran) |
748| `Notification` | N/A, shows stderr to user only |
749| `UserPromptSubmit` | Blocks prompt processing, erases prompt, shows stderr to user only |
750| `Stop` | Blocks stoppage, shows stderr to Claude |
751| `SubagentStop` | Blocks stoppage, shows stderr to Claude subagent |
752| `PreCompact` | N/A, shows stderr to user only |
753| `SessionStart` | N/A, shows stderr to user only |
754| `SessionEnd` | N/A, shows stderr to user only |
755 1834
756### Advanced: JSON Output1835Runs when an MCP server requests user input mid-task. By default, Claude Code shows an interactive dialog for the user to respond. Hooks can intercept this request and respond programmatically, skipping the dialog entirely.
757 1836
758Hooks can return structured JSON in `stdout` for more sophisticated control.1837The matcher field matches against the MCP server name.
759 1838
760<Warning>1839#### Elicitation input
761 JSON output is only processed when the hook exits with code 0. If your hook
762 exits with code 2 (blocking error), `stderr` text is used directly—any JSON in `stdout`
763 is ignored. For other non-zero exit codes, only `stderr` is shown to the user in verbose mode (ctrl+o).
764</Warning>
765 1840
766#### Common JSON Fields1841In addition to the [common input fields](#common-input-fields), Elicitation hooks receive `mcp_server_name`, `message`, and optional `mode`, `url`, `elicitation_id`, and `requested_schema` fields.
767 1842
768All hook types can include these optional fields:1843For form-mode elicitation (the most common case):
769 1844
770```json theme={null}1845```json theme={null}
771{1846{
772 "continue": true, // Whether Claude should continue after hook execution (default: true)1847 "session_id": "abc123",
773 "stopReason": "string", // Message shown when continue is false1848 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
774 1849 "cwd": "/Users/...",
775 "suppressOutput": true, // Hide stdout from transcript mode (default: false)1850 "permission_mode": "default",
776 "systemMessage": "string" // Optional warning message shown to the user1851 "hook_event_name": "Elicitation",
1852 "mcp_server_name": "my-mcp-server",
1853 "message": "Please provide your credentials",
1854 "mode": "form",
1855 "requested_schema": {
1856 "type": "object",
1857 "properties": {
1858 "username": { "type": "string", "title": "Username" }
1859 }
1860 }
777}1861}
778```1862```
779 1863
780If `continue` is false, Claude stops processing after the hooks run.1864For URL-mode elicitation (browser-based authentication):
781
782* For `PreToolUse`, this is different from `"permissionDecision": "deny"`, which
783 only blocks a specific tool call and provides automatic feedback to Claude.
784* For `PostToolUse`, this is different from `"decision": "block"`, which
785 provides automated feedback to Claude.
786* For `UserPromptSubmit`, this prevents the prompt from being processed.
787* For `Stop` and `SubagentStop`, this takes precedence over any
788 `"decision": "block"` output.
789* In all cases, `"continue" = false` takes precedence over any
790 `"decision": "block"` output.
791
792`stopReason` accompanies `continue` with a reason shown to the user, not shown
793to Claude.
794
795#### `PreToolUse` Decision Control
796
797`PreToolUse` hooks can control whether a tool call proceeds.
798 1865
799* `"allow"` bypasses the permission system. `permissionDecisionReason` is shown1866```json theme={null}
800 to the user but not to Claude.1867{
801* `"deny"` prevents the tool call from executing. `permissionDecisionReason` is1868 "session_id": "abc123",
802 shown to Claude.1869 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
803* `"ask"` asks the user to confirm the tool call in the UI.1870 "cwd": "/Users/...",
804 `permissionDecisionReason` is shown to the user but not to Claude.1871 "permission_mode": "default",
1872 "hook_event_name": "Elicitation",
1873 "mcp_server_name": "my-mcp-server",
1874 "message": "Please authenticate",
1875 "mode": "url",
1876 "url": "https://auth.example.com/login"
1877}
1878```
805 1879
806Additionally, hooks can modify tool inputs before execution using `updatedInput`:1880#### Elicitation output
807 1881
808* `updatedInput` modifies the tool's input parameters before the tool executes1882To respond programmatically without showing the dialog, return a JSON object with `hookSpecificOutput`:
809* Combine with `"permissionDecision": "allow"` to modify the input and auto-approve the tool call
810* Combine with `"permissionDecision": "ask"` to modify the input and show it to the user for confirmation
811 1883
812```json theme={null}1884```json theme={null}
813{1885{
814 "hookSpecificOutput": {1886 "hookSpecificOutput": {
815 "hookEventName": "PreToolUse",1887 "hookEventName": "Elicitation",
816 "permissionDecision": "allow",1888 "action": "accept",
817 "permissionDecisionReason": "My reason here",1889 "content": {
818 "updatedInput": {1890 "username": "alice"
819 "field_to_modify": "new value"
820 }1891 }
821 }1892 }
822}1893}
823```1894```
824 1895
825<Note>1896| Field | Values | Description |
826 The `decision` and `reason` fields are deprecated for PreToolUse hooks.1897| :-------- | :---------------------------- | :--------------------------------------------------------------- |
827 Use `hookSpecificOutput.permissionDecision` and1898| `action` | `accept`, `decline`, `cancel` | Whether to accept, decline, or cancel the request |
828 `hookSpecificOutput.permissionDecisionReason` instead. The deprecated fields1899| `content` | object | Form field values to submit. Only used when `action` is `accept` |
829 `"approve"` and `"block"` map to `"allow"` and `"deny"` respectively.1900
830</Note>1901Exit code 2 denies the elicitation and shows stderr to the user.
831 1902
832#### `PermissionRequest` Decision Control1903### ElicitationResult
833 1904
834`PermissionRequest` hooks can allow or deny permission requests shown to the user.1905Runs after a user responds to an MCP elicitation. Hooks can observe, modify, or block the response before it is sent back to the MCP server.
835 1906
836* For `"behavior": "allow"` you can also optionally pass in an `"updatedInput"` that modifies the tool's input parameters before the tool executes.1907The matcher field matches against the MCP server name.
837* For `"behavior": "deny"` you can also optionally pass in a `"message"` string that tells the model why the permission was denied, and a boolean `"interrupt"` which will stop Claude.1908
1909#### ElicitationResult input
1910
1911In addition to the [common input fields](#common-input-fields), ElicitationResult hooks receive `mcp_server_name`, `action`, and optional `mode`, `elicitation_id`, and `content` fields.
838 1912
839```json theme={null}1913```json theme={null}
840{1914{
841 "hookSpecificOutput": {1915 "session_id": "abc123",
842 "hookEventName": "PermissionRequest",1916 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
843 "decision": {1917 "cwd": "/Users/...",
844 "behavior": "allow",1918 "permission_mode": "default",
845 "updatedInput": {1919 "hook_event_name": "ElicitationResult",
846 "command": "npm run lint"1920 "mcp_server_name": "my-mcp-server",
847 }1921 "action": "accept",
848 }1922 "content": { "username": "alice" },
849 }1923 "mode": "form",
1924 "elicitation_id": "elicit-123"
850}1925}
851```1926```
852 1927
853#### `PostToolUse` Decision Control1928#### ElicitationResult output
854
855`PostToolUse` hooks can provide feedback to Claude after tool execution.
856 1929
857* `"block"` automatically prompts Claude with `reason`.1930To override the user's response, return a JSON object with `hookSpecificOutput`:
858* `undefined` does nothing. `reason` is ignored.
859* `"hookSpecificOutput.additionalContext"` adds context for Claude to consider.
860 1931
861```json theme={null}1932```json theme={null}
862{1933{
863 "decision": "block" | undefined,
864 "reason": "Explanation for decision",
865 "hookSpecificOutput": {1934 "hookSpecificOutput": {
866 "hookEventName": "PostToolUse",1935 "hookEventName": "ElicitationResult",
867 "additionalContext": "Additional information for Claude"1936 "action": "decline",
1937 "content": {}
868 }1938 }
869}1939}
870```1940```
871 1941
872#### `UserPromptSubmit` Decision Control1942| Field | Values | Description |
873 1943| :-------- | :---------------------------- | :--------------------------------------------------------------------- |
874`UserPromptSubmit` hooks can control whether a user prompt is processed and add context.1944| `action` | `accept`, `decline`, `cancel` | Overrides the user's action |
1945| `content` | object | Overrides form field values. Only meaningful when `action` is `accept` |
1946
1947Exit code 2 blocks the response, changing the effective action to `decline`.
1948
1949## Prompt-based hooks
1950
1951In addition to command and HTTP hooks, Claude Code supports prompt-based hooks (`type: "prompt"`) that use an LLM to evaluate whether to allow or block an action, and agent hooks (`type: "agent"`) that spawn an agentic verifier with tool access. Not all events support every hook type.
1952
1953Events that support all four hook types (`command`, `http`, `prompt`, and `agent`):
1954
1955* `PermissionRequest`
1956* `PostToolUse`
1957* `PostToolUseFailure`
1958* `PreToolUse`
1959* `Stop`
1960* `SubagentStop`
1961* `TaskCompleted`
1962* `TaskCreated`
1963* `UserPromptSubmit`
1964
1965Events that support `command` and `http` hooks but not `prompt` or `agent`:
1966
1967* `ConfigChange`
1968* `CwdChanged`
1969* `Elicitation`
1970* `ElicitationResult`
1971* `FileChanged`
1972* `InstructionsLoaded`
1973* `Notification`
1974* `PostCompact`
1975* `PreCompact`
1976* `SessionEnd`
1977* `StopFailure`
1978* `SubagentStart`
1979* `TeammateIdle`
1980* `WorktreeCreate`
1981* `WorktreeRemove`
1982
1983`SessionStart` supports only `command` hooks.
875 1984
876**Adding context (exit code 0):**1985### How prompt-based hooks work
877There are two ways to add context to the conversation:
878 1986
8791. **Plain text stdout** (simpler): Any non-JSON text written to stdout is added1987Instead of executing a Bash command, prompt-based hooks:
880 as context. This is the easiest way to inject information.
881 1988
8822. **JSON with `additionalContext`** (structured): Use the JSON format below for19891. Send the hook input and your prompt to a Claude model, Haiku by default
883 more control. The `additionalContext` field is added as context.19902. The LLM responds with structured JSON containing a decision
19913. Claude Code processes the decision automatically
884 1992
885Both methods work with exit code 0. Plain stdout is shown as hook output in1993### Prompt hook configuration
886the transcript; `additionalContext` is added more discretely.
887 1994
888**Blocking prompts:**1995Set `type` to `"prompt"` and provide a `prompt` string instead of a `command`. Use the `$ARGUMENTS` placeholder to inject the hook's JSON input data into your prompt text. Claude Code sends the combined prompt and input to a fast Claude model, which returns a JSON decision.
889 1996
890* `"decision": "block"` prevents the prompt from being processed. The submitted1997This `Stop` hook asks the LLM to evaluate whether all tasks are complete before allowing Claude to finish:
891 prompt is erased from context. `"reason"` is shown to the user but not added
892 to context.
893* `"decision": undefined` (or omitted) allows the prompt to proceed normally.
894 1998
895```json theme={null}1999```json theme={null}
896{2000{
897 "decision": "block" | undefined,2001 "hooks": {
898 "reason": "Explanation for decision",2002 "Stop": [
899 "hookSpecificOutput": {2003 {
900 "hookEventName": "UserPromptSubmit",2004 "hooks": [
901 "additionalContext": "My additional context here"2005 {
2006 "type": "prompt",
2007 "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."
2008 }
2009 ]
2010 }
2011 ]
902 }2012 }
903}2013}
904```2014```
905 2015
906<Note>2016| Field | Required | Description |
907 The JSON format isn't required for simple use cases. To add context, you can print plain text to stdout with exit code 0. Use JSON when you need to2017| :-------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
908 block prompts or want more structured control.2018| `type` | yes | Must be `"prompt"` |
909</Note>2019| `prompt` | yes | The prompt text to send to the LLM. Use `$ARGUMENTS` as a placeholder for the hook input JSON. If `$ARGUMENTS` is not present, input JSON is appended to the prompt |
910 2020| `model` | no | Model to use for evaluation. Defaults to a fast model |
911#### `Stop`/`SubagentStop` Decision Control2021| `timeout` | no | Timeout in seconds. Default: 30 |
912 2022
913`Stop` and `SubagentStop` hooks can control whether Claude must continue.2023### Response schema
914 2024
915* `"block"` prevents Claude from stopping. You must populate `reason` for Claude2025The LLM must respond with JSON containing:
916 to know how to proceed.
917* `undefined` allows Claude to stop. `reason` is ignored.
918 2026
919```json theme={null}2027```json theme={null}
920{2028{
921 "decision": "block" | undefined,2029 "ok": true | false,
922 "reason": "Must be provided when Claude is blocked from stopping"2030 "reason": "Explanation for the decision"
923}2031}
924```2032```
925 2033
926#### `SessionStart` Decision Control2034| Field | Description |
2035| :------- | :--------------------------------------------------------- |
2036| `ok` | `true` allows the action, `false` prevents it |
2037| `reason` | Required when `ok` is `false`. Explanation shown to Claude |
927 2038
928`SessionStart` hooks allow you to load in context at the start of a session.2039### Example: Multi-criteria Stop hook
929 2040
930* `"hookSpecificOutput.additionalContext"` adds the string to the context.2041This `Stop` hook uses a detailed prompt to check three conditions before allowing Claude to stop. If `"ok"` is `false`, Claude continues working with the provided reason as its next instruction. `SubagentStop` hooks use the same format to evaluate whether a [subagent](/en/sub-agents) should stop:
931* Multiple hooks' `additionalContext` values are concatenated.
932 2042
933```json theme={null}2043```json theme={null}
934{2044{
935 "hookSpecificOutput": {2045 "hooks": {
936 "hookEventName": "SessionStart",2046 "Stop": [
937 "additionalContext": "My additional context here"2047 {
2048 "hooks": [
2049 {
2050 "type": "prompt",
2051 "prompt": "You are evaluating whether Claude should stop working. Context: $ARGUMENTS\n\nAnalyze the conversation and determine if:\n1. All user-requested tasks are complete\n2. Any errors need to be addressed\n3. Follow-up work is needed\n\nRespond with JSON: {\"ok\": true} to allow stopping, or {\"ok\": false, \"reason\": \"your explanation\"} to continue working.",
2052 "timeout": 30
2053 }
2054 ]
2055 }
2056 ]
938 }2057 }
939}2058}
940```2059```
941 2060
942#### `SessionEnd` Decision Control2061## Agent-based hooks
943
944`SessionEnd` hooks run when a session ends. They cannot block session termination
945but can perform cleanup tasks.
946 2062
947#### Exit Code Example: Bash Command Validation2063Agent-based hooks (`type: "agent"`) are like prompt-based hooks but with multi-turn tool access. Instead of a single LLM call, an agent hook spawns a subagent that can read files, search code, and inspect the codebase to verify conditions. Agent hooks support the same events as prompt-based hooks.
948 2064
949```python theme={null}2065### How agent hooks work
950#!/usr/bin/env python3
951import json
952import re
953import sys
954 2066
955# Define validation rules as a list of (regex pattern, message) tuples2067When an agent hook fires:
956VALIDATION_RULES = [
957 (
958 r"\bgrep\b(?!.*\|)",
959 "Use 'rg' (ripgrep) instead of 'grep' for better performance and features",
960 ),
961 (
962 r"\bfind\s+\S+\s+-name\b",
963 "Use 'rg --files | rg pattern' or 'rg --files -g pattern' instead of 'find -name' for better performance",
964 ),
965]
966 2068
20691. Claude Code spawns a subagent with your prompt and the hook's JSON input
20702. The subagent can use tools like Read, Grep, and Glob to investigate
20713. After up to 50 turns, the subagent returns a structured `{ "ok": true/false }` decision
20724. Claude Code processes the decision the same way as a prompt hook
967 2073
968def validate_command(command: str) -> list[str]:2074Agent hooks are useful when verification requires inspecting actual files or test output, not just evaluating the hook input data alone.
969 issues = []
970 for pattern, message in VALIDATION_RULES:
971 if re.search(pattern, command):
972 issues.append(message)
973 return issues
974 2075
2076### Agent hook configuration
975 2077
976try:2078Set `type` to `"agent"` and provide a `prompt` string. The configuration fields are the same as [prompt hooks](#prompt-hook-configuration), with a longer default timeout:
977 input_data = json.load(sys.stdin)
978except json.JSONDecodeError as e:
979 print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
980 sys.exit(1)
981 2079
982tool_name = input_data.get("tool_name", "")2080| Field | Required | Description |
983tool_input = input_data.get("tool_input", {})2081| :-------- | :------- | :------------------------------------------------------------------------------------------ |
984command = tool_input.get("command", "")2082| `type` | yes | Must be `"agent"` |
2083| `prompt` | yes | Prompt describing what to verify. Use `$ARGUMENTS` as a placeholder for the hook input JSON |
2084| `model` | no | Model to use. Defaults to a fast model |
2085| `timeout` | no | Timeout in seconds. Default: 60 |
985 2086
986if tool_name != "Bash" or not command:2087The response schema is the same as prompt hooks: `{ "ok": true }` to allow or `{ "ok": false, "reason": "..." }` to block.
987 sys.exit(1)
988 2088
989# Validate the command2089This `Stop` hook verifies that all unit tests pass before allowing Claude to finish:
990issues = validate_command(command)
991 2090
992if issues:2091```json theme={null}
993 for message in issues:2092{
994 print(f"• {message}", file=sys.stderr)2093 "hooks": {
995 # Exit code 2 blocks tool call and shows stderr to Claude2094 "Stop": [
996 sys.exit(2)2095 {
2096 "hooks": [
2097 {
2098 "type": "agent",
2099 "prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
2100 "timeout": 120
2101 }
2102 ]
2103 }
2104 ]
2105 }
2106}
997```2107```
998 2108
999#### JSON Output Example: UserPromptSubmit to Add Context and Validation2109## Run hooks in the background
1000
1001<Note>
1002 For `UserPromptSubmit` hooks, you can inject context using either method:
1003
1004 * **Plain text stdout** with exit code 0: Simplest approach, prints text
1005 * **JSON output** with exit code 0: Use `"decision": "block"` to reject prompts,
1006 or `additionalContext` for structured context injection
1007 2110
1008 Remember: Exit code 2 only uses `stderr` for the error message. To block using2111By default, hooks block Claude's execution until they complete. For long-running tasks like deployments, test suites, or external API calls, set `"async": true` to run the hook in the background while Claude continues working. Async hooks cannot block or control Claude's behavior: response fields like `decision`, `permissionDecision`, and `continue` have no effect, because the action they would have controlled has already completed.
1009 JSON (with a custom reason), use `"decision": "block"` with exit code 0.
1010</Note>
1011 2112
1012```python theme={null}2113### Configure an async hook
1013#!/usr/bin/env python3
1014import json
1015import sys
1016import re
1017import datetime
1018
1019# Load input from stdin
1020try:
1021 input_data = json.load(sys.stdin)
1022except json.JSONDecodeError as e:
1023 print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
1024 sys.exit(1)
1025
1026prompt = input_data.get("prompt", "")
1027
1028# Check for sensitive patterns
1029sensitive_patterns = [
1030 (r"(?i)\b(password|secret|key|token)\s*[:=]", "Prompt contains potential secrets"),
1031]
1032
1033for pattern, message in sensitive_patterns:
1034 if re.search(pattern, prompt):
1035 # Use JSON output to block with a specific reason
1036 output = {
1037 "decision": "block",
1038 "reason": f"Security policy violation: {message}. Please rephrase your request without sensitive information."
1039 }
1040 print(json.dumps(output))
1041 sys.exit(0)
1042 2114
1043# Add current time to context2115Add `"async": true` to a command hook's configuration to run it in the background without blocking Claude. This field is only available on `type: "command"` hooks.
1044context = f"Current time: {datetime.datetime.now()}"
1045print(context)
1046 2116
1047"""2117This hook runs a test script after every `Write` tool call. Claude continues working immediately while `run-tests.sh` executes for up to 120 seconds. When the script finishes, its output is delivered on the next conversation turn:
1048The following is also equivalent:
1049print(json.dumps({
1050 "hookSpecificOutput": {
1051 "hookEventName": "UserPromptSubmit",
1052 "additionalContext": context,
1053 },
1054}))
1055"""
1056 2118
1057# Allow the prompt to proceed with the additional context2119```json theme={null}
1058sys.exit(0)2120{
2121 "hooks": {
2122 "PostToolUse": [
2123 {
2124 "matcher": "Write",
2125 "hooks": [
2126 {
2127 "type": "command",
2128 "command": "/path/to/run-tests.sh",
2129 "async": true,
2130 "timeout": 120
2131 }
2132 ]
2133 }
2134 ]
2135 }
2136}
1059```2137```
1060 2138
1061#### JSON Output Example: PreToolUse with Approval2139The `timeout` field sets the maximum time in seconds for the background process. If not specified, async hooks use the same 10-minute default as sync hooks.
1062 2140
1063```python theme={null}2141### How async hooks execute
1064#!/usr/bin/env python3
1065import json
1066import sys
1067 2142
1068# Load input from stdin2143When an async hook fires, Claude Code starts the hook process and immediately continues without waiting for it to finish. The hook receives the same JSON input via stdin as a synchronous hook.
1069try:
1070 input_data = json.load(sys.stdin)
1071except json.JSONDecodeError as e:
1072 print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
1073 sys.exit(1)
1074 2144
1075tool_name = input_data.get("tool_name", "")2145After the background process exits, if the hook produced a JSON response with a `systemMessage` or `additionalContext` field, that content is delivered to Claude as context on the next conversation turn.
1076tool_input = input_data.get("tool_input", {})
1077 2146
1078# Example: Auto-approve file reads for documentation files2147Async hook completion notifications are suppressed by default. To see them, enable verbose mode with `Ctrl+O` or start Claude Code with `--verbose`.
1079if tool_name == "Read":
1080 file_path = tool_input.get("file_path", "")
1081 if file_path.endswith((".md", ".mdx", ".txt", ".json")):
1082 # Use JSON output to auto-approve the tool call
1083 output = {
1084 "decision": "approve",
1085 "reason": "Documentation file auto-approved",
1086 "suppressOutput": True # Don't show in verbose mode
1087 }
1088 print(json.dumps(output))
1089 sys.exit(0)
1090 2148
1091# For other cases, let the normal permission flow proceed2149### Example: run tests after file changes
1092sys.exit(0)
1093```
1094 2150
1095## Working with MCP Tools2151This hook starts a test suite in the background whenever Claude writes a file, then reports the results back to Claude when the tests finish. Save this script to `.claude/hooks/run-tests-async.sh` in your project and make it executable with `chmod +x`:
1096 2152
1097Claude Code hooks work seamlessly with2153```bash theme={null}
1098[Model Context Protocol (MCP) tools](/en/mcp). When MCP servers2154#!/bin/bash
1099provide tools, they appear with a special naming pattern that you can match in2155# run-tests-async.sh
1100your hooks.
1101 2156
1102### MCP Tool Naming2157# Read hook input from stdin
2158INPUT=$(cat)
2159FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
1103 2160
1104MCP tools follow the pattern `mcp__<server>__<tool>`, for example:2161# Only run tests for source files
2162if [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.js ]]; then
2163 exit 0
2164fi
1105 2165
1106* `mcp__memory__create_entities` - Memory server's create entities tool2166# Run tests and report results via systemMessage
1107* `mcp__filesystem__read_file` - Filesystem server's read file tool2167RESULT=$(npm test 2>&1)
1108* `mcp__github__search_repositories` - GitHub server's search tool2168EXIT_CODE=$?
1109 2169
1110### Configuring Hooks for MCP Tools2170if [ $EXIT_CODE -eq 0 ]; then
2171 echo "{\"systemMessage\": \"Tests passed after editing $FILE_PATH\"}"
2172else
2173 echo "{\"systemMessage\": \"Tests failed after editing $FILE_PATH: $RESULT\"}"
2174fi
2175```
1111 2176
1112You can target specific MCP tools or entire MCP servers:2177Then add this configuration to `.claude/settings.json` in your project root. The `async: true` flag lets Claude keep working while tests run:
1113 2178
1114```json theme={null}2179```json theme={null}
1115{2180{
1116 "hooks": {2181 "hooks": {
1117 "PreToolUse": [2182 "PostToolUse": [
1118 {
1119 "matcher": "mcp__memory__.*",
1120 "hooks": [
1121 {
1122 "type": "command",
1123 "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
1124 }
1125 ]
1126 },
1127 {2183 {
1128 "matcher": "mcp__.*__write.*",2184 "matcher": "Write|Edit",
1129 "hooks": [2185 "hooks": [
1130 {2186 {
1131 "type": "command",2187 "type": "command",
1132 "command": "/home/user/scripts/validate-mcp-write.py"2188 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/run-tests-async.sh",
2189 "async": true,
2190 "timeout": 300
1133 }2191 }
1134 ]2192 ]
1135 }2193 }
1138}2196}
1139```2197```
1140 2198
1141## Examples2199### Limitations
1142
1143<Tip>
1144 For practical examples including code formatting, notifications, and file protection, see [More Examples](/en/hooks-guide#more-examples) in the get started guide.
1145</Tip>
1146
1147## Security Considerations
1148
1149### Disclaimer
1150
1151**USE AT YOUR OWN RISK**: Claude Code hooks execute arbitrary shell commands on
1152your system automatically. By using hooks, you acknowledge that:
1153 2200
1154* You are solely responsible for the commands you configure2201Async hooks have several constraints compared to synchronous hooks:
1155* Hooks can modify, delete, or access any files your user account can access
1156* Malicious or poorly written hooks can cause data loss or system damage
1157* Anthropic provides no warranty and assumes no liability for any damages
1158 resulting from hook usage
1159* You should thoroughly test hooks in a safe environment before production use
1160 2202
1161Always review and understand any hook commands before adding them to your2203* Only `type: "command"` hooks support `async`. Prompt-based hooks cannot run asynchronously.
1162configuration.2204* Async hooks cannot block tool calls or return decisions. By the time the hook completes, the triggering action has already proceeded.
2205* Hook output is delivered on the next conversation turn. If the session is idle, the response waits until the next user interaction.
2206* Each execution creates a separate background process. There is no deduplication across multiple firings of the same async hook.
1163 2207
1164### Security Best Practices2208## Security considerations
1165 2209
1166Here are some key practices for writing more secure hooks:2210### Disclaimer
1167
11681. **Validate and sanitize inputs** - Never trust input data blindly
11692. **Always quote shell variables** - Use `"$VAR"` not `$VAR`
11703. **Block path traversal** - Check for `..` in file paths
11714. **Use absolute paths** - Specify full paths for scripts (use
1172 "\$CLAUDE\_PROJECT\_DIR" for the project path)
11735. **Skip sensitive files** - Avoid `.env`, `.git/`, keys, etc.
1174
1175### Configuration Safety
1176
1177Direct edits to hooks in settings files don't take effect immediately. Claude
1178Code:
1179
11801. Captures a snapshot of hooks at startup
11812. Uses this snapshot throughout the session
11823. Warns if hooks are modified externally
11834. Requires review in `/hooks` menu for changes to apply
1184
1185This prevents malicious hook modifications from affecting your current session.
1186
1187## Hook Execution Details
1188
1189* **Timeout**: 60-second execution limit by default, configurable per command.
1190 * A timeout for an individual command does not affect the other commands.
1191* **Parallelization**: All matching hooks run in parallel
1192* **Deduplication**: Multiple identical hook commands are deduplicated automatically
1193* **Environment**: Runs in current directory with Claude Code's environment
1194 * The `CLAUDE_PROJECT_DIR` environment variable is available and contains the
1195 absolute path to the project root directory (where Claude Code was started)
1196 * The `CLAUDE_CODE_REMOTE` environment variable indicates whether the hook is running in a remote (web) environment (`"true"`) or local CLI environment (not set or empty). Use this to run different logic based on execution context.
1197* **Input**: JSON via stdin
1198* **Output**:
1199 * PreToolUse/PermissionRequest/PostToolUse/Stop/SubagentStop: Progress shown in verbose mode (ctrl+o)
1200 * Notification/SessionEnd: Logged to debug only (`--debug`)
1201 * UserPromptSubmit/SessionStart: stdout added as context for Claude
1202
1203## Debugging
1204 2211
1205### Basic Troubleshooting2212Command hooks run with your system user's full permissions.
1206 2213
1207If your hooks aren't working:2214<Warning>
2215 Command hooks execute shell commands with your full user permissions. They can modify, delete, or access any files your user account can access. Review and test all hook commands before adding them to your configuration.
2216</Warning>
1208 2217
12091. **Check configuration** - Run `/hooks` to see if your hook is registered2218### Security best practices
12102. **Verify syntax** - Ensure your JSON settings are valid
12113. **Test commands** - Run hook commands manually first
12124. **Check permissions** - Make sure scripts are executable
12135. **Review logs** - Use `claude --debug` to see hook execution details
1214 2219
1215Common issues:2220Keep these practices in mind when writing hooks:
1216 2221
1217* **Quotes not escaped** - Use `\"` inside JSON strings2222* **Validate and sanitize inputs**: never trust input data blindly
1218* **Wrong matcher** - Check tool names match exactly (case-sensitive)2223* **Always quote shell variables**: use `"$VAR"` not `$VAR`
1219* **Command not found** - Use full paths for scripts2224* **Block path traversal**: check for `..` in file paths
2225* **Use absolute paths**: specify full paths for scripts, using `"$CLAUDE_PROJECT_DIR"` for the project root
2226* **Skip sensitive files**: avoid `.env`, `.git/`, keys, etc.
1220 2227
1221### Advanced Debugging2228## Windows PowerShell tool
1222 2229
1223For complex hook issues:2230On Windows, you can run individual hooks in PowerShell by setting `"shell": "powershell"` on a command hook. Hooks spawn PowerShell directly, so this works regardless of whether `CLAUDE_CODE_USE_POWERSHELL_TOOL` is set. Claude Code auto-detects `pwsh.exe` (PowerShell 7+) with a fallback to `powershell.exe` (5.1).
1224 2231
12251. **Inspect hook execution** - Use `claude --debug` to see detailed hook2232```json theme={null}
1226 execution2233{
12272. **Validate JSON schemas** - Test hook input/output with external tools2234 "hooks": {
12283. **Check environment variables** - Verify Claude Code's environment is correct2235 "PostToolUse": [
12294. **Test edge cases** - Try hooks with unusual file paths or inputs2236 {
12305. **Monitor system resources** - Check for resource exhaustion during hook2237 "matcher": "Write",
1231 execution2238 "hooks": [
12326. **Use structured logging** - Implement logging in your hook scripts2239 {
2240 "type": "command",
2241 "shell": "powershell",
2242 "command": "Write-Host 'File written'"
2243 }
2244 ]
2245 }
2246 ]
2247 }
2248}
2249```
1233 2250
1234### Debug Output Example2251## Debug hooks
1235 2252
1236Use `claude --debug` to see hook execution details:2253Run `claude --debug` to see hook execution details, including which hooks matched, their exit codes, and output. Toggle verbose mode with `Ctrl+O` to see hook progress in the transcript.
1237 2254
1238```2255```text theme={null}
1239[DEBUG] Executing hooks for PostToolUse:Write2256[DEBUG] Executing hooks for PostToolUse:Write
1240[DEBUG] Getting matching hook commands for PostToolUse with query: Write2257[DEBUG] Getting matching hook commands for PostToolUse with query: Write
1241[DEBUG] Found 1 hook matchers in settings2258[DEBUG] Found 1 hook matchers in settings
1242[DEBUG] Matched 1 hooks for query "Write"2259[DEBUG] Matched 1 hooks for query "Write"
1243[DEBUG] Found 1 hook commands to execute2260[DEBUG] Found 1 hook commands to execute
1244[DEBUG] Executing hook command: <Your command> with timeout 60000ms2261[DEBUG] Executing hook command: <Your command> with timeout 600000ms
1245[DEBUG] Hook command completed with status 0: <Your stdout>2262[DEBUG] Hook command completed with status 0: <Your stdout>
1246```2263```
1247 2264
1248Progress messages appear in verbose mode (ctrl+o) showing:2265For troubleshooting common issues like hooks not firing, infinite Stop hook loops, or configuration errors, see [Limitations and troubleshooting](/en/hooks-guide#limitations-and-troubleshooting) in the guide.
1249
1250* Which hook is running
1251* Command being executed
1252* Success/failure status
1253* Output or error messages
1254
1255
1256
1257> To find navigation and other pages in this documentation, fetch the llms.txt file at: https://code.claude.com/docs/llms.txt