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
13Hooks 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.
14
9## Hook lifecycle15## Hook lifecycle
10 16
11Hooks fire at specific points during a Claude Code session.17Hooks 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:
12 18
13<div style={{maxWidth: "500px", margin: "0 auto"}}>19<div style={{maxWidth: "500px", margin: "0 auto"}}>
14 <Frame>20 <Frame>
15 <img src="https://mintcdn.com/claude-code/z2YM37Ycg6eMbID3/images/hooks-lifecycle.png?fit=max&auto=format&n=z2YM37Ycg6eMbID3&q=85&s=5c25fedbc3db6f8882af50c3cc478c32" alt="Hook lifecycle diagram showing the sequence of hooks from SessionStart through the agentic loop to SessionEnd" data-og-width="8876" width="8876" data-og-height="12492" height="12492" data-path="images/hooks-lifecycle.png" data-optimize="true" data-opv="3" srcset="https://mintcdn.com/claude-code/z2YM37Ycg6eMbID3/images/hooks-lifecycle.png?w=280&fit=max&auto=format&n=z2YM37Ycg6eMbID3&q=85&s=62406fcd5d4a189cc8842ee1bd946b84 280w, https://mintcdn.com/claude-code/z2YM37Ycg6eMbID3/images/hooks-lifecycle.png?w=560&fit=max&auto=format&n=z2YM37Ycg6eMbID3&q=85&s=fa3049022a6973c5f974e0f95b28169d 560w, https://mintcdn.com/claude-code/z2YM37Ycg6eMbID3/images/hooks-lifecycle.png?w=840&fit=max&auto=format&n=z2YM37Ycg6eMbID3&q=85&s=bd2890897db61a03160b93d4f972ff8e 840w, https://mintcdn.com/claude-code/z2YM37Ycg6eMbID3/images/hooks-lifecycle.png?w=1100&fit=max&auto=format&n=z2YM37Ycg6eMbID3&q=85&s=7ae8e098340479347135e39df4a13454 1100w, https://mintcdn.com/claude-code/z2YM37Ycg6eMbID3/images/hooks-lifecycle.png?w=1650&fit=max&auto=format&n=z2YM37Ycg6eMbID3&q=85&s=848a8606aab22c2ccaa16b6a18431e32 1650w, https://mintcdn.com/claude-code/z2YM37Ycg6eMbID3/images/hooks-lifecycle.png?w=2500&fit=max&auto=format&n=z2YM37Ycg6eMbID3&q=85&s=f3a9ef7feb61fa8fe362005aa185efbc 2500w" />21 <img src="https://mintcdn.com/claude-code/lBsitdsGyD9caWJQ/images/hooks-lifecycle.svg?fit=max&auto=format&n=lBsitdsGyD9caWJQ&q=85&s=be3486ef2cf2563eb213b6cbbce93982" alt="Hook lifecycle diagram showing the sequence of hooks from SessionStart through the agentic loop (PreToolUse, PermissionRequest, PostToolUse, SubagentStart/Stop, TaskCompleted) to PostCompact and SessionEnd, with Elicitation and ElicitationResult nested inside MCP tool execution and WorktreeCreate, WorktreeRemove, Notification, ConfigChange, and InstructionsLoaded as standalone async events" width="520" height="1100" data-path="images/hooks-lifecycle.svg" />
16 </Frame>22 </Frame>
17</div>23</div>
18 24
19| Hook | When it fires |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.
20| :------------------- | :------------------------------ |26
21| `SessionStart` | Session begins or resumes |27| Event | When it fires |
22| `UserPromptSubmit` | User submits a prompt |28| :------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------- |
23| `PreToolUse` | Before tool execution |29| `SessionStart` | When a session begins or resumes |
24| `PermissionRequest` | When permission dialog appears |30| `UserPromptSubmit` | When you submit a prompt, before Claude processes it |
25| `PostToolUse` | After tool succeeds |31| `PreToolUse` | Before a tool call executes. Can block it |
26| `PostToolUseFailure` | After tool fails |32| `PermissionRequest` | When a permission dialog appears |
27| `SubagentStart` | When spawning a subagent |33| `PostToolUse` | After a tool call succeeds |
28| `SubagentStop` | When subagent finishes |34| `PostToolUseFailure` | After a tool call fails |
29| `Stop` | Claude finishes responding |35| `Notification` | When Claude Code sends a notification |
36| `SubagentStart` | When a subagent is spawned |
37| `SubagentStop` | When a subagent finishes |
38| `Stop` | When Claude finishes responding |
39| `TeammateIdle` | When an [agent team](/en/agent-teams) teammate is about to go idle |
40| `TaskCompleted` | When a task is being marked as completed |
41| `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 |
42| `ConfigChange` | When a configuration file changes during a session |
43| `WorktreeCreate` | When a worktree is being created via `--worktree` or `isolation: "worktree"`. Replaces default git behavior |
44| `WorktreeRemove` | When a worktree is being removed, either at session exit or when a subagent finishes |
30| `PreCompact` | Before context compaction |45| `PreCompact` | Before context compaction |
31| `SessionEnd` | Session terminates |46| `PostCompact` | After context compaction completes |
32| `Notification` | Claude Code sends notifications |47| `Elicitation` | When an MCP server requests user input during a tool call |
33 48| `ElicitationResult` | After a user responds to an MCP elicitation, before the response is sent back to the server |
34## Configuration49| `SessionEnd` | When a session terminates |
35
36Claude Code hooks are configured in your [settings files](/en/settings):
37
38* `~/.claude/settings.json` - User settings
39* `.claude/settings.json` - Project settings
40* `.claude/settings.local.json` - Local project settings (not committed)
41* Managed policy settings
42
43<Note>
44 Enterprise administrators can use `allowManagedHooksOnly` to block user, project, and plugin hooks. See [Hook configuration](/en/settings#hook-configuration).
45</Note>
46 50
47### Structure51### How a hook resolves
48 52
49Hooks are organized by matchers, where each matcher can have multiple hooks:53To see how these pieces fit together, consider this `PreToolUse` hook that blocks destructive shell commands. The hook runs `block-rm.sh` before every Bash tool call:
50 54
51```json theme={null}55```json theme={null}
52{56{
53 "hooks": {57 "hooks": {
54 "EventName": [58 "PreToolUse": [
55 {59 {
56 "matcher": "ToolPattern",60 "matcher": "Bash",
57 "hooks": [61 "hooks": [
58 {62 {
59 "type": "command",63 "type": "command",
60 "command": "your-command-here"64 "command": ".claude/hooks/block-rm.sh"
61 }65 }
62 ]66 ]
63 }67 }
66}70}
67```71```
68 72
69* **matcher**: Pattern to match tool names, case-sensitive (only applicable for73The script reads the JSON input from stdin, extracts the command, and returns a `permissionDecision` of `"deny"` if it contains `rm -rf`:
70 `PreToolUse`, `PermissionRequest`, and `PostToolUse`)
71 * Simple strings match exactly: `Write` matches only the Write tool
72 * Supports regex: `Edit|Write` or `Notebook.*`
73 * Use `*` to match all tools. You can also use empty string (`""`) or leave
74 `matcher` blank.
75* **hooks**: Array of hooks to execute when the pattern matches
76 * `type`: Hook execution type - `"command"` for bash commands or `"prompt"` for LLM-based evaluation
77 * `command`: (For `type: "command"`) The bash command to execute (can use `$CLAUDE_PROJECT_DIR` environment variable)
78 * `prompt`: (For `type: "prompt"`) The prompt to send to the LLM for evaluation
79 * `timeout`: (Optional) How long a hook should run, in seconds, before canceling that specific hook
80
81For events like `UserPromptSubmit`, `Stop`, `SubagentStop`, and `Setup`
82that don't use matchers, you can omit the matcher field:
83 74
84```json theme={null}75```bash theme={null}
85{76#!/bin/bash
86 "hooks": {77# .claude/hooks/block-rm.sh
87 "UserPromptSubmit": [78COMMAND=$(jq -r '.tool_input.command')
88 {79
89 "hooks": [80if echo "$COMMAND" | grep -q 'rm -rf'; then
90 {81 jq -n '{
91 "type": "command",82 hookSpecificOutput: {
92 "command": "/path/to/prompt-validator.py"83 hookEventName: "PreToolUse",
93 }84 permissionDecision: "deny",
94 ]85 permissionDecisionReason: "Destructive command blocked by hook"
95 }
96 ]
97 }86 }
98}87 }'
88else
89 exit 0 # allow the command
90fi
99```91```
100 92
101### Project-Specific Hook Scripts93Now suppose Claude Code decides to run `Bash "rm -rf /tmp/build"`. Here's what happens:
102
103You can use the environment variable `CLAUDE_PROJECT_DIR` (only available when
104Claude Code spawns the hook command) to reference scripts stored in your project,
105ensuring they work regardless of Claude's current directory:
106
107```json theme={null}
108{
109 "hooks": {
110 "PostToolUse": [
111 {
112 "matcher": "Write|Edit",
113 "hooks": [
114 {
115 "type": "command",
116 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-style.sh"
117 }
118 ]
119 }
120 ]
121 }
122}
123```
124 94
125### Plugin hooks95<Frame>
96 <img src="https://mintcdn.com/claude-code/c5r9_6tjPMzFdDDT/images/hook-resolution.svg?fit=max&auto=format&n=c5r9_6tjPMzFdDDT&q=85&s=ad667ee6d86ab2276aa48a4e73e220df" alt="Hook resolution flow: PreToolUse event fires, matcher checks for Bash match, hook handler runs, result returns to Claude Code" width="780" height="290" data-path="images/hook-resolution.svg" />
97</Frame>
126 98
127[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.99<Steps>
100 <Step title="Event fires">
101 The `PreToolUse` event fires. Claude Code sends the tool input as JSON on stdin to the hook:
128 102
129**How plugin hooks work**:103 ```json theme={null}
104 { "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... }
105 ```
106 </Step>
130 107
131* 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.108 <Step title="Matcher checks">
132* When a plugin is enabled, its hooks are merged with user and project hooks109 The matcher `"Bash"` matches the tool name, so `block-rm.sh` runs. If you omit the matcher or use `"*"`, the hook runs on every occurrence of the event. Hooks only skip when a matcher is defined and doesn't match.
133* Multiple hooks from different sources can respond to the same event110 </Step>
134* Plugin hooks use the `${CLAUDE_PLUGIN_ROOT}` environment variable to reference plugin files
135 111
136**Example plugin hook configuration**:112 <Step title="Hook handler runs">
113 The script extracts `"rm -rf /tmp/build"` from the input and finds `rm -rf`, so it prints a decision to stdout:
137 114
138```json theme={null}115 ```json theme={null}
139{
140 "description": "Automatic code formatting",
141 "hooks": {
142 "PostToolUse": [
143 {
144 "matcher": "Write|Edit",
145 "hooks": [
146 {116 {
147 "type": "command",117 "hookSpecificOutput": {
148 "command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",118 "hookEventName": "PreToolUse",
149 "timeout": 30119 "permissionDecision": "deny",
150 }120 "permissionDecisionReason": "Destructive command blocked by hook"
151 ]
152 }121 }
153 ]
154 }122 }
155}123 ```
156```
157
158<Note>
159 Plugin hooks use the same format as regular hooks with an optional `description` field to explain the hook's purpose.
160</Note>
161
162<Note>
163 Plugin hooks run alongside your custom hooks. If multiple hooks match an event, they all execute in parallel.
164</Note>
165 124
166**Environment variables for plugins**:125 If the command had been safe (like `npm test`), the script would hit `exit 0` instead, which tells Claude Code to allow the tool call with no further action.
126 </Step>
167 127
168* `${CLAUDE_PLUGIN_ROOT}`: Absolute path to the plugin directory128 <Step title="Claude Code acts on the result">
169* `${CLAUDE_PROJECT_DIR}`: Project root directory (same as for project hooks)129 Claude Code reads the JSON decision, blocks the tool call, and shows Claude the reason.
170* All standard environment variables are available130 </Step>
131</Steps>
171 132
172See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.133The [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.
173
174### Hooks in skills and agents
175
176In 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.
177 134
178**Supported events**: `PreToolUse`, `PostToolUse`, and `Stop`135## Configuration
179 136
180**Example in a Skill**:137Hooks are defined in JSON settings files. The configuration has three levels of nesting:
181 138
182```yaml theme={null}1391. Choose a [hook event](#hook-events) to respond to, like `PreToolUse` or `Stop`
183name: secure-operations1402. Add a [matcher group](#matcher-patterns) to filter when it fires, like "only for the Bash tool"
184description: Perform operations with security checks1413. Define one or more [hook handlers](#hook-handler-fields) to run when matched
185hooks:
186 PreToolUse:
187 - matcher: "Bash"
188 hooks:
189 - type: command
190 command: "./scripts/security-check.sh"
191```
192 142
193**Example in an agent**:143See [How a hook resolves](#how-a-hook-resolves) above for a complete walkthrough with an annotated example.
194 144
195```yaml theme={null}145<Note>
196name: code-reviewer146 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.
197description: Review code changes147</Note>
198hooks:
199 PostToolUse:
200 - matcher: "Edit|Write"
201 hooks:
202 - type: command
203 command: "./scripts/run-linter.sh"
204```
205 148
206Component-scoped hooks follow the same configuration format as settings-based hooks but are automatically cleaned up when the component finishes executing.149### Hook locations
207 150
208**Additional option for skills:**151Where you define a hook determines its scope:
209 152
210* `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, not for agents.153| Location | Scope | Shareable |
154| :--------------------------------------------------------- | :---------------------------- | :--------------------------------- |
155| `~/.claude/settings.json` | All your projects | No, local to your machine |
156| `.claude/settings.json` | Single project | Yes, can be committed to the repo |
157| `.claude/settings.local.json` | Single project | No, gitignored |
158| Managed policy settings | Organization-wide | Yes, admin-controlled |
159| [Plugin](/en/plugins) `hooks/hooks.json` | When plugin is enabled | Yes, bundled with the plugin |
160| [Skill](/en/skills) or [agent](/en/sub-agents) frontmatter | While the component is active | Yes, defined in the component file |
211 161
212## Prompt-Based Hooks162For 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).
213 163
214In 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.164### Matcher patterns
215 165
216### How prompt-based hooks work166The `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:
217 167
218Instead of executing a bash command, prompt-based hooks:168| Event | What the matcher filters | Example matcher values |
169| :-------------------------------------------------------------------------------------------------------------------- | :------------------------ | :--------------------------------------------------------------------------------- |
170| `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest` | tool name | `Bash`, `Edit\|Write`, `mcp__.*` |
171| `SessionStart` | how the session started | `startup`, `resume`, `clear`, `compact` |
172| `SessionEnd` | why the session ended | `clear`, `logout`, `prompt_input_exit`, `bypass_permissions_disabled`, `other` |
173| `Notification` | notification type | `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog` |
174| `SubagentStart` | agent type | `Bash`, `Explore`, `Plan`, or custom agent names |
175| `PreCompact` | what triggered compaction | `manual`, `auto` |
176| `SubagentStop` | agent type | same values as `SubagentStart` |
177| `ConfigChange` | configuration source | `user_settings`, `project_settings`, `local_settings`, `policy_settings`, `skills` |
178| `UserPromptSubmit`, `Stop`, `TeammateIdle`, `TaskCompleted`, `WorktreeCreate`, `WorktreeRemove`, `InstructionsLoaded` | no matcher support | always fires on every occurrence |
219 179
2201. Send the hook input and your prompt to a fast LLM (Haiku)180The 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.
2212. The LLM responds with structured JSON containing a decision
2223. Claude Code processes the decision automatically
223 181
224### Configuration182This example runs a linting script only when Claude writes or edits a file:
225 183
226```json theme={null}184```json theme={null}
227{185{
228 "hooks": {186 "hooks": {
229 "Stop": [187 "PostToolUse": [
230 {188 {
189 "matcher": "Edit|Write",
231 "hooks": [190 "hooks": [
232 {191 {
233 "type": "prompt",192 "type": "command",
234 "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."193 "command": "/path/to/lint-check.sh"
235 }194 }
236 ]195 ]
237 }196 }
244}199}
245```200```
246 201
247**Fields:**202`UserPromptSubmit`, `Stop`, `TeammateIdle`, `TaskCompleted`, `WorktreeCreate`, `WorktreeRemove`, and `InstructionsLoaded` don't support matchers and always fire on every occurrence. If you add a `matcher` field to these events, it is silently ignored.
248
249* `type`: Must be `"prompt"`
250* `prompt`: The prompt text to send to the LLM
251 * Use `$ARGUMENTS` as a placeholder for the hook input JSON
252 * If `$ARGUMENTS` is not present, input JSON is appended to the prompt
253* `timeout`: (Optional) Timeout in seconds (default: 30 seconds)
254
255### Response schema
256
257The LLM must respond with JSON containing:
258 203
259```json theme={null}204#### Match MCP tools
260{
261 "ok": true | false,
262 "reason": "Explanation for the decision"
263}
264```
265 205
266**Response fields:**206[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.
267 207
268* `ok`: `true` allows the action, `false` prevents it208MCP tools follow the naming pattern `mcp__<server>__<tool>`, for example:
269* `reason`: Required when `ok` is `false`. Explanation shown to Claude
270 209
271### Supported hook events210* `mcp__memory__create_entities`: Memory server's create entities tool
211* `mcp__filesystem__read_file`: Filesystem server's read file tool
212* `mcp__github__search_repositories`: GitHub server's search tool
272 213
273Prompt-based hooks work with any hook event, but are most useful for:214Use regex patterns to target specific MCP tools or groups of tools:
274 215
275* **Stop**: Intelligently decide if Claude should continue working216* `mcp__memory__.*` matches all tools from the `memory` server
276* **SubagentStop**: Evaluate if a subagent has completed its task217* `mcp__.*__write.*` matches any tool containing "write" from any server
277* **UserPromptSubmit**: Validate user prompts with LLM assistance
278* **PreToolUse**: Make context-aware permission decisions
279* **PermissionRequest**: Intelligently allow or deny permission dialogs
280 218
281### Example: Intelligent Stop hook219This example logs all memory server operations and validates write operations from any MCP server:
282 220
283```json theme={null}221```json theme={null}
284{222{
285 "hooks": {223 "hooks": {
286 "Stop": [224 "PreToolUse": [
287 {225 {
226 "matcher": "mcp__memory__.*",
288 "hooks": [227 "hooks": [
289 {228 {
290 "type": "prompt",229 "type": "command",
291 "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.",230 "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
292 "timeout": 30
293 }
294 ]
295 }231 }
296 ]232 ]
297 }233 },
298}
299```
300
301### Example: SubagentStop with custom logic
302
303```json theme={null}
304{
305 "hooks": {
306 "SubagentStop": [
307 {234 {
235 "matcher": "mcp__.*__write.*",
308 "hooks": [236 "hooks": [
309 {237 {
310 "type": "prompt",238 "type": "command",
311 "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."239 "command": "/home/user/scripts/validate-mcp-write.py"
312 }240 }
313 ]241 ]
314 }242 }
317}245}
318```246```
319 247
320### Comparison with bash command hooks248### Hook handler fields
321 249
322| Feature | Bash Command Hooks | Prompt-Based Hooks |250Each 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:
323| --------------------- | ----------------------- | ------------------------------ |
324| **Execution** | Runs bash script | Queries LLM |
325| **Decision logic** | You implement in code | LLM evaluates context |
326| **Setup complexity** | Requires script file | Configure prompt |
327| **Context awareness** | Limited to script logic | Natural language understanding |
328| **Performance** | Fast (local execution) | Slower (API call) |
329| **Use case** | Deterministic rules | Context-aware decisions |
330 251
331### Best practices252* **[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.
253* **[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.
254* **[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).
255* **[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).
332 256
333* **Be specific in prompts**: Clearly state what you want the LLM to evaluate257#### Common fields
334* **Include decision criteria**: List the factors the LLM should consider
335* **Test your prompts**: Verify the LLM makes correct decisions for your use cases
336* **Set appropriate timeouts**: Default is 30 seconds, adjust if needed
337* **Use for complex decisions**: Bash hooks are better for simple, deterministic rules
338 258
339See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.259These fields apply to all hook types:
340 260
341## Hook Events261| Field | Required | Description |
262| :-------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------- |
263| `type` | yes | `"command"`, `"http"`, `"prompt"`, or `"agent"` |
264| `timeout` | no | Seconds before canceling. Defaults: 600 for command, 30 for prompt, 60 for agent |
265| `statusMessage` | no | Custom spinner message displayed while the hook runs |
266| `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) |
342 267
343### PreToolUse268#### Command hook fields
344 269
345Runs after Claude creates tool parameters and before processing the tool call.270In addition to the [common fields](#common-fields), command hooks accept these fields:
346 271
347**Common matchers:**272| Field | Required | Description |
273| :-------- | :------- | :------------------------------------------------------------------------------------------------------------------ |
274| `command` | yes | Shell command to execute |
275| `async` | no | If `true`, runs in the background without blocking. See [Run hooks in the background](#run-hooks-in-the-background) |
348 276
349* `Task` - Subagent tasks (see [subagents documentation](/en/sub-agents))277#### HTTP hook fields
350* `Bash` - Shell commands
351* `Glob` - File pattern matching
352* `Grep` - Content search
353* `Read` - File reading
354* `Edit` - File editing
355* `Write` - File writing
356* `WebFetch`, `WebSearch` - Web operations
357 278
358Use [PreToolUse decision control](#pretooluse-decision-control) to allow, deny, or ask for permission to use the tool.279In addition to the [common fields](#common-fields), HTTP hooks accept these fields:
359 280
360### PermissionRequest281| Field | Required | Description |
282| :--------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
283| `url` | yes | URL to send the POST request to |
284| `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 |
285| `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 |
361 286
362Runs when the user is shown a permission dialog.287Claude 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.
363Use [PermissionRequest decision control](#permissionrequest-decision-control) to allow or deny on behalf of the user.
364 288
365Recognizes the same matcher values as PreToolUse.289Error 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"`.
366 290
367### PostToolUse291This example sends `PreToolUse` events to a local validation service, authenticating with a token from the `MY_TOKEN` environment variable:
368 292
369Runs immediately after a tool completes successfully.293```json theme={null}
294{
295 "hooks": {
296 "PreToolUse": [
297 {
298 "matcher": "Bash",
299 "hooks": [
300 {
301 "type": "http",
302 "url": "http://localhost:8080/hooks/pre-tool-use",
303 "timeout": 30,
304 "headers": {
305 "Authorization": "Bearer $MY_TOKEN"
306 },
307 "allowedEnvVars": ["MY_TOKEN"]
308 }
309 ]
310 }
311 ]
312 }
313}
314```
370 315
371Recognizes the same matcher values as PreToolUse.316#### Prompt and agent hook fields
372 317
373### Notification318In addition to the [common fields](#common-fields), prompt and agent hooks accept these fields:
374 319
375Runs when Claude Code sends notifications. Supports matchers to filter by notification type.320| Field | Required | Description |
321| :------- | :------- | :------------------------------------------------------------------------------------------ |
322| `prompt` | yes | Prompt text to send to the model. Use `$ARGUMENTS` as a placeholder for the hook input JSON |
323| `model` | no | Model to use for evaluation. Defaults to a fast model |
376 324
377**Common matchers:**325All 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.
378 326
379* `permission_prompt` - Permission requests from Claude Code327### Reference scripts by path
380* `idle_prompt` - When Claude is waiting for user input (after 60+ seconds of idle time)
381* `auth_success` - Authentication success notifications
382* `elicitation_dialog` - When Claude Code needs input for MCP tool elicitation
383 328
384You can use matchers to run different hooks for different notification types, or omit the matcher to run hooks for all notifications.329Use environment variables to reference hook scripts relative to the project or plugin root, regardless of the working directory when the hook runs:
385 330
386**Example: Different notifications for different types**331* `$CLAUDE_PROJECT_DIR`: the project root. Wrap in quotes to handle paths with spaces.
332* `${CLAUDE_PLUGIN_ROOT}`: the plugin's root directory, for scripts bundled with a [plugin](/en/plugins).
387 333
388```json theme={null}334<Tabs>
389{335 <Tab title="Project scripts">
336 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:
337
338 ```json theme={null}
339 {
390 "hooks": {340 "hooks": {
391 "Notification": [341 "PostToolUse": [
392 {342 {
393 "matcher": "permission_prompt",343 "matcher": "Write|Edit",
394 "hooks": [344 "hooks": [
395 {345 {
396 "type": "command",346 "type": "command",
397 "command": "/path/to/permission-alert.sh"347 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-style.sh"
398 }348 }
399 ]349 ]
400 },350 }
351 ]
352 }
353 }
354 ```
355 </Tab>
356
357 <Tab title="Plugin scripts">
358 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.
359
360 This example runs a formatting script bundled with the plugin:
361
362 ```json theme={null}
401 {363 {
402 "matcher": "idle_prompt",364 "description": "Automatic code formatting",
365 "hooks": {
366 "PostToolUse": [
367 {
368 "matcher": "Write|Edit",
403 "hooks": [369 "hooks": [
404 {370 {
405 "type": "command",371 "type": "command",
406 "command": "/path/to/idle-notification.sh"372 "command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",
373 "timeout": 30
407 }374 }
408 ]375 ]
409 }376 }
410 ]377 ]
411 }378 }
412}379 }
413```380 ```
414 381
415### UserPromptSubmit382 See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.
383 </Tab>
384</Tabs>
416 385
417Runs when the user submits a prompt, before Claude processes it. This allows you386### Hooks in skills and agents
418to add additional context based on the prompt/conversation, validate prompts, or
419block certain types of prompts.
420 387
421### Stop388In 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.
422 389
423Runs when the main Claude Code agent has finished responding. Does not run if390All hook events are supported. For subagents, `Stop` hooks are automatically converted to `SubagentStop` since that is the event that fires when a subagent completes.
424the stoppage occurred due to a user interrupt.
425 391
426### SubagentStop392Hooks use the same configuration format as settings-based hooks but are scoped to the component's lifetime and cleaned up when it finishes.
427 393
428Runs when a Claude Code subagent (Task tool call) has finished responding.394This skill defines a `PreToolUse` hook that runs a security validation script before each `Bash` command:
429 395
430### PreCompact396```yaml theme={null}
397---
398name: secure-operations
399description: Perform operations with security checks
400hooks:
401 PreToolUse:
402 - matcher: "Bash"
403 hooks:
404 - type: command
405 command: "./scripts/security-check.sh"
406---
407```
431 408
432Runs before Claude Code is about to run a compact operation.409Agents use the same format in their YAML frontmatter.
433 410
434**Matchers:**411### The `/hooks` menu
435 412
436* `manual` - Invoked from `/compact`413Type `/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.
437* `auto` - Invoked from auto-compact (due to full context window)
438 414
439### Setup415The 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:
440 416
441Runs when Claude Code is invoked with repository setup and maintenance flags (`--init`, `--init-only`, or `--maintenance`). Use this hook for operations you don't want on every session—such as installing dependencies, running migrations, or periodic maintenance tasks.417* `User`: from `~/.claude/settings.json`
418* `Project`: from `.claude/settings.json`
419* `Local`: from `.claude/settings.local.json`
420* `Plugin`: from a plugin's `hooks/hooks.json`
421* `Session`: registered in memory for the current session
422* `Built-in`: registered internally by Claude Code
442 423
443<Note>424Selecting 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.
444 Use **Setup** hooks for one-time or occasional operations (dependency installation, migrations, cleanup). Use **SessionStart** hooks for things you want on every session (loading context, setting environment variables). Setup hooks require explicit flags because running them automatically would slow down every session start.
445</Note>
446 425
447**Matchers:**426### Disable or remove hooks
448 427
449* `init` - Invoked from `--init` or `--init-only` flags428To remove a hook, delete its entry from the settings JSON file.
450* `maintenance` - Invoked from `--maintenance` flag
451 429
452Setup hooks have access to the `CLAUDE_ENV_FILE` environment variable for persisting environment variables, similar to SessionStart hooks.430To 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.
453 431
454### SessionStart432The `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.
455 433
456Runs when Claude Code starts a new session or resumes an existing session (which434Direct edits to hooks in settings files are normally picked up automatically by the file watcher.
457currently does start a new session under the hood). Useful for loading development context like existing issues or recent changes to your codebase, or setting up environment variables.
458 435
459<Note>436## Hook input and output
460 For one-time operations like installing dependencies or running migrations, use [Setup hooks](#setup) instead. SessionStart runs on every session, so keep these hooks fast.
461</Note>
462 437
463**Matchers:**438Command 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.
464 439
465* `startup` - Invoked from startup440### Common input fields
466* `resume` - Invoked from `--resume`, `--continue`, or `/resume`
467* `clear` - Invoked from `/clear`
468* `compact` - Invoked from auto or manual compact.
469 441
470#### Persisting environment variables442All hook 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.
471 443
472SessionStart 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.444| Field | Description |
445| :---------------- | :----------------------------------------------------------------------------------------------------------------------------------------- |
446| `session_id` | Current session identifier |
447| `transcript_path` | Path to conversation JSON |
448| `cwd` | Current working directory when the hook is invoked |
449| `permission_mode` | Current [permission mode](/en/permissions#permission-modes): `"default"`, `"plan"`, `"acceptEdits"`, `"dontAsk"`, or `"bypassPermissions"` |
450| `hook_event_name` | Name of the event that fired |
473 451
474**Example: Setting individual environment variables**452When running with `--agent` or inside a subagent, two additional fields are included:
475 453
476```bash theme={null}454| Field | Description |
477#!/bin/bash455| :----------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
456| `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. |
457| `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 458
479if [ -n "$CLAUDE_ENV_FILE" ]; then459For example, a `PreToolUse` hook for a Bash command receives this on stdin:
480 echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
481 echo 'export API_KEY=your-api-key' >> "$CLAUDE_ENV_FILE"
482 echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
483fi
484 460
485exit 0461```json theme={null}
462{
463 "session_id": "abc123",
464 "transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
465 "cwd": "/home/user/my-project",
466 "permission_mode": "default",
467 "hook_event_name": "PreToolUse",
468 "tool_name": "Bash",
469 "tool_input": {
470 "command": "npm test"
471 }
472}
486```473```
487 474
488**Example: Persisting all environment changes from the hook**475The `tool_name` and `tool_input` fields are event-specific. Each [hook event](#hook-events) section documents the additional fields for that event.
489 476
490When your setup modifies the environment (for example, `nvm use`), capture and persist all changes by diffing the environment:477### Exit code output
491 478
492```bash theme={null}479The exit code from your hook command tells Claude Code whether the action should proceed, be blocked, or be ignored.
493#!/bin/bash
494 480
495ENV_BEFORE=$(export -p | sort)481**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.
496 482
497# Run your setup commands that modify the environment483**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.
498source ~/.nvm/nvm.sh
499nvm use 20
500 484
501if [ -n "$CLAUDE_ENV_FILE" ]; then485**Any other exit code** is a non-blocking error. stderr is shown in verbose mode (`Ctrl+O`) and execution continues.
502 ENV_AFTER=$(export -p | sort)486
503 comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"487For example, a hook command script that blocks dangerous Bash commands:
488
489```bash theme={null}
490#!/bin/bash
491# Reads JSON input from stdin, checks the command
492command=$(jq -r '.tool_input.command' < /dev/stdin)
493
494if [[ "$command" == rm* ]]; then
495 echo "Blocked: rm commands are not allowed" >&2
496 exit 2 # Blocking error: tool call is prevented
504fi497fi
505 498
506exit 0499exit 0 # Success: tool call proceeds
507```500```
508 501
509Any variables written to this file will be available in all subsequent bash commands that Claude Code executes during the session.502#### Exit code 2 behavior per event
503
504Exit 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.
505
506| Hook event | Can block? | What happens on exit 2 |
507| :------------------- | :--------- | :---------------------------------------------------------------------------- |
508| `PreToolUse` | Yes | Blocks the tool call |
509| `PermissionRequest` | Yes | Denies the permission |
510| `UserPromptSubmit` | Yes | Blocks prompt processing and erases the prompt |
511| `Stop` | Yes | Prevents Claude from stopping, continues the conversation |
512| `SubagentStop` | Yes | Prevents the subagent from stopping |
513| `TeammateIdle` | Yes | Prevents the teammate from going idle (teammate continues working) |
514| `TaskCompleted` | Yes | Prevents the task from being marked as completed |
515| `ConfigChange` | Yes | Blocks the configuration change from taking effect (except `policy_settings`) |
516| `PostToolUse` | No | Shows stderr to Claude (tool already ran) |
517| `PostToolUseFailure` | No | Shows stderr to Claude (tool already failed) |
518| `Notification` | No | Shows stderr to user only |
519| `SubagentStart` | No | Shows stderr to user only |
520| `SessionStart` | No | Shows stderr to user only |
521| `SessionEnd` | No | Shows stderr to user only |
522| `PreCompact` | No | Shows stderr to user only |
523| `PostCompact` | No | Shows stderr to user only |
524| `Elicitation` | Yes | Denies the elicitation |
525| `ElicitationResult` | Yes | Blocks the response (action becomes decline) |
526| `WorktreeCreate` | Yes | Any non-zero exit code causes worktree creation to fail |
527| `WorktreeRemove` | No | Failures are logged in debug mode only |
528| `InstructionsLoaded` | No | Exit code is ignored |
529
530### HTTP response handling
531
532HTTP hooks use HTTP status codes and response bodies instead of exit codes and stdout:
533
534* **2xx with an empty body**: success, equivalent to exit code 0 with no output
535* **2xx with a plain text body**: success, the text is added as context
536* **2xx with a JSON body**: success, parsed using the same [JSON output](#json-output) schema as command hooks
537* **Non-2xx status**: non-blocking error, execution continues
538* **Connection failure or timeout**: non-blocking error, execution continues
539
540Unlike 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.
541
542### JSON output
543
544Exit 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.
510 545
511<Note>546<Note>
512 `CLAUDE_ENV_FILE` is only available for SessionStart hooks. Other hook types do not have access to this variable.547 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.
513</Note>548</Note>
514 549
515### SessionEnd550Your 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.
516
517Runs when a Claude Code session ends. Useful for cleanup tasks, logging session
518statistics, or saving session state.
519 551
520The `reason` field in the hook input will be one of:552The JSON object supports three kinds of fields:
521 553
522* `clear` - Session cleared with /clear command554* **Universal fields** like `continue` work across all events. These are listed in the table below.
523* `logout` - User logged out555* **Top-level `decision` and `reason`** are used by some events to block or provide feedback.
524* `prompt_input_exit` - User exited while prompt input was visible556* **`hookSpecificOutput`** is a nested object for events that need richer control. It requires a `hookEventName` field set to the event name.
525* `other` - Other exit reasons
526 557
527## Hook Input558| Field | Default | Description |
559| :--------------- | :------ | :------------------------------------------------------------------------------------------------------------------------- |
560| `continue` | `true` | If `false`, Claude stops processing entirely after the hook runs. Takes precedence over any event-specific decision fields |
561| `stopReason` | none | Message shown to the user when `continue` is `false`. Not shown to Claude |
562| `suppressOutput` | `false` | If `true`, hides stdout from verbose mode output |
563| `systemMessage` | none | Warning message shown to the user |
528 564
529Hooks receive JSON data via stdin containing session information and565To stop Claude entirely regardless of event type:
530event-specific data:
531 566
532```typescript theme={null}567```json theme={null}
533{568{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }
534 // Common fields
535 session_id: string
536 transcript_path: string // Path to conversation JSON
537 cwd: string // The current working directory when the hook is invoked
538 permission_mode: string // Current permission mode: "default", "plan", "acceptEdits", "dontAsk", or "bypassPermissions"
539
540 // Event-specific fields
541 hook_event_name: string
542 ...
543}
544```569```
545 570
546### PreToolUse Input571#### Decision control
547 572
548The exact schema for `tool_input` depends on the tool. Here are examples for commonly hooked tools.573Not 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:
549 574
550#### Bash tool575| Events | Decision pattern | Key fields |
576| :------------------------------------------------------------------------------------ | :----------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
577| UserPromptSubmit, PostToolUse, PostToolUseFailure, Stop, SubagentStop, ConfigChange | Top-level `decision` | `decision: "block"`, `reason` |
578| TeammateIdle, 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 |
579| PreToolUse | `hookSpecificOutput` | `permissionDecision` (allow/deny/ask), `permissionDecisionReason` |
580| PermissionRequest | `hookSpecificOutput` | `decision.behavior` (allow/deny) |
581| WorktreeCreate | stdout path | Hook prints absolute path to created worktree. Non-zero exit fails creation |
582| Elicitation | `hookSpecificOutput` | `action` (accept/decline/cancel), `content` (form field values for accept) |
583| ElicitationResult | `hookSpecificOutput` | `action` (accept/decline/cancel), `content` (form field values override) |
584| WorktreeRemove, Notification, SessionEnd, PreCompact, PostCompact, InstructionsLoaded | None | No decision control. Used for side effects like logging or cleanup |
551 585
552The Bash tool is the most commonly hooked tool for command validation:586Here are examples of each pattern in action:
553 587
554```json theme={null}588<Tabs>
589 <Tab title="Top-level decision">
590 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:
591
592 ```json theme={null}
593 {
594 "decision": "block",
595 "reason": "Test suite must pass before proceeding"
596 }
597 ```
598 </Tab>
599
600 <Tab title="PreToolUse">
601 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.
602
603 ```json theme={null}
604 {
605 "hookSpecificOutput": {
606 "hookEventName": "PreToolUse",
607 "permissionDecision": "deny",
608 "permissionDecisionReason": "Database writes are not allowed"
609 }
610 }
611 ```
612 </Tab>
613
614 <Tab title="PermissionRequest">
615 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.
616
617 ```json theme={null}
618 {
619 "hookSpecificOutput": {
620 "hookEventName": "PermissionRequest",
621 "decision": {
622 "behavior": "allow",
623 "updatedInput": {
624 "command": "npm run lint"
625 }
626 }
627 }
628 }
629 ```
630 </Tab>
631</Tabs>
632
633For 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).
634
635## Hook events
636
637Each 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.
638
639### SessionStart
640
641Runs 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.
642
643SessionStart runs on every session, so keep these hooks fast. Only `type: "command"` hooks are supported.
644
645The matcher value corresponds to how the session was initiated:
646
647| Matcher | When it fires |
648| :-------- | :------------------------------------- |
649| `startup` | New session |
650| `resume` | `--resume`, `--continue`, or `/resume` |
651| `clear` | `/clear` |
652| `compact` | Auto or manual compaction |
653
654#### SessionStart input
655
656In 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.
657
658```json theme={null}
555{659{
556 "session_id": "abc123",660 "session_id": "abc123",
557 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",661 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
558 "cwd": "/Users/...",662 "cwd": "/Users/...",
559 "permission_mode": "default",663 "permission_mode": "default",
560 "hook_event_name": "PreToolUse",664 "hook_event_name": "SessionStart",
561 "tool_name": "Bash",665 "source": "startup",
562 "tool_input": {666 "model": "claude-sonnet-4-6"
563 "command": "psql -c 'SELECT * FROM users'",
564 "description": "Query the users table",
565 "timeout": 120000
566 },
567 "tool_use_id": "toolu_01ABC123..."
568}667}
569```668```
570 669
571| Field | Type | Description |670#### SessionStart decision control
572| :------------------ | :------ | :-------------------------------------------- |671
573| `command` | string | The shell command to execute |672Any 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:
574| `description` | string | Optional description of what the command does |673
575| `timeout` | number | Optional timeout in milliseconds |674| Field | Description |
576| `run_in_background` | boolean | Whether to run the command in background |675| :------------------ | :------------------------------------------------------------------------ |
676| `additionalContext` | String added to Claude's context. Multiple hooks' values are concatenated |
677
678```json theme={null}
679{
680 "hookSpecificOutput": {
681 "hookEventName": "SessionStart",
682 "additionalContext": "My additional context here"
683 }
684}
685```
686
687#### Persist environment variables
688
689SessionStart 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.
690
691To set individual environment variables, write `export` statements to `CLAUDE_ENV_FILE`. Use append (`>>`) to preserve variables set by other hooks:
692
693```bash theme={null}
694#!/bin/bash
695
696if [ -n "$CLAUDE_ENV_FILE" ]; then
697 echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
698 echo 'export DEBUG_LOG=true' >> "$CLAUDE_ENV_FILE"
699 echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
700fi
701
702exit 0
703```
704
705To capture all environment changes from setup commands, compare the exported variables before and after:
706
707```bash theme={null}
708#!/bin/bash
709
710ENV_BEFORE=$(export -p | sort)
711
712# Run your setup commands that modify the environment
713source ~/.nvm/nvm.sh
714nvm use 20
715
716if [ -n "$CLAUDE_ENV_FILE" ]; then
717 ENV_AFTER=$(export -p | sort)
718 comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"
719fi
720
721exit 0
722```
723
724Any variables written to this file will be available in all subsequent Bash commands that Claude Code executes during the session.
725
726<Note>
727 `CLAUDE_ENV_FILE` is available for SessionStart hooks. Other hook types do not have access to this variable.
728</Note>
729
730### InstructionsLoaded
731
732Fires 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.
577 733
578#### Write tool734InstructionsLoaded does not support matchers and fires on every load occurrence.
735
736#### InstructionsLoaded input
737
738In addition to the [common input fields](#common-input-fields), InstructionsLoaded hooks receive these fields:
739
740| Field | Description |
741| :------------------ | :-------------------------------------------------------------------------------------------------------- |
742| `file_path` | Absolute path to the instruction file that was loaded |
743| `memory_type` | Scope of the file: `"User"`, `"Project"`, `"Local"`, or `"Managed"` |
744| `load_reason` | Why the file was loaded: `"session_start"`, `"nested_traversal"`, `"path_glob_match"`, or `"include"` |
745| `globs` | Path glob patterns from the file's `paths:` frontmatter, if any. Present only for `path_glob_match` loads |
746| `trigger_file_path` | Path to the file whose access triggered this load, for lazy loads |
747| `parent_file_path` | Path to the parent instruction file that included this one, for `include` loads |
579 748
580```json theme={null}749```json theme={null}
581{750{
582 "session_id": "abc123",751 "session_id": "abc123",
583 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",752 "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
584 "cwd": "/Users/...",753 "cwd": "/Users/my-project",
585 "permission_mode": "default",754 "permission_mode": "default",
586 "hook_event_name": "PreToolUse",755 "hook_event_name": "InstructionsLoaded",
587 "tool_name": "Write",756 "file_path": "/Users/my-project/CLAUDE.md",
588 "tool_input": {757 "memory_type": "Project",
589 "file_path": "/path/to/file.txt",758 "load_reason": "session_start"
590 "content": "file content"
591 },
592 "tool_use_id": "toolu_01ABC123..."
593}759}
594```760```
595 761
596| Field | Type | Description |762#### InstructionsLoaded decision control
597| :---------- | :----- | :--------------------------------- |
598| `file_path` | string | Absolute path to the file to write |
599| `content` | string | Content to write to the file |
600 763
601#### Edit tool764InstructionsLoaded hooks have no decision control. They cannot block or modify instruction loading. Use this event for audit logging, compliance tracking, or observability.
765
766### UserPromptSubmit
767
768Runs when the user submits a prompt, before Claude processes it. This allows you
769to add additional context based on the prompt/conversation, validate prompts, or
770block certain types of prompts.
771
772#### UserPromptSubmit input
773
774In addition to the [common input fields](#common-input-fields), UserPromptSubmit hooks receive the `prompt` field containing the text the user submitted.
602 775
603```json theme={null}776```json theme={null}
604{777{
606 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",779 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
607 "cwd": "/Users/...",780 "cwd": "/Users/...",
608 "permission_mode": "default",781 "permission_mode": "default",
609 "hook_event_name": "PreToolUse",782 "hook_event_name": "UserPromptSubmit",
610 "tool_name": "Edit",783 "prompt": "Write a function to calculate the factorial of a number"
611 "tool_input": {784}
612 "file_path": "/path/to/file.txt",785```
613 "old_string": "original text",786
614 "new_string": "replacement text"787#### UserPromptSubmit decision control
788
789`UserPromptSubmit` hooks can control whether a user prompt is processed and add context. All [JSON output fields](#json-output) are available.
790
791There are two ways to add context to the conversation on exit code 0:
792
793* **Plain text stdout**: any non-JSON text written to stdout is added as context
794* **JSON with `additionalContext`**: use the JSON format below for more control. The `additionalContext` field is added as context
795
796Plain stdout is shown as hook output in the transcript. The `additionalContext` field is added more discretely.
797
798To block a prompt, return a JSON object with `decision` set to `"block"`:
799
800| Field | Description |
801| :------------------ | :----------------------------------------------------------------------------------------------------------------- |
802| `decision` | `"block"` prevents the prompt from being processed and erases it from context. Omit to allow the prompt to proceed |
803| `reason` | Shown to the user when `decision` is `"block"`. Not added to context |
804| `additionalContext` | String added to Claude's context |
805
806```json theme={null}
807{
808 "decision": "block",
809 "reason": "Explanation for decision",
810 "hookSpecificOutput": {
811 "hookEventName": "UserPromptSubmit",
812 "additionalContext": "My additional context here"
813 }
814}
815```
816
817<Note>
818 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
819 block prompts or want more structured control.
820</Note>
821
822### PreToolUse
823
824Runs after Claude creates tool parameters and before processing the tool call. Matches on tool name: `Bash`, `Edit`, `Write`, `Read`, `Glob`, `Grep`, `Agent`, `WebFetch`, `WebSearch`, and any [MCP tool names](#match-mcp-tools).
825
826Use [PreToolUse decision control](#pretooluse-decision-control) to allow, deny, or ask for permission to use the tool.
827
828#### PreToolUse input
829
830In 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:
831
832##### Bash
833
834Executes shell commands.
835
836| Field | Type | Example | Description |
837| :------------------ | :------ | :----------------- | :-------------------------------------------- |
838| `command` | string | `"npm test"` | The shell command to execute |
839| `description` | string | `"Run test suite"` | Optional description of what the command does |
840| `timeout` | number | `120000` | Optional timeout in milliseconds |
841| `run_in_background` | boolean | `false` | Whether to run the command in background |
842
843##### Write
844
845Creates or overwrites a file.
846
847| Field | Type | Example | Description |
848| :---------- | :----- | :-------------------- | :--------------------------------- |
849| `file_path` | string | `"/path/to/file.txt"` | Absolute path to the file to write |
850| `content` | string | `"file content"` | Content to write to the file |
851
852##### Edit
853
854Replaces a string in an existing file.
855
856| Field | Type | Example | Description |
857| :------------ | :------ | :-------------------- | :--------------------------------- |
858| `file_path` | string | `"/path/to/file.txt"` | Absolute path to the file to edit |
859| `old_string` | string | `"original text"` | Text to find and replace |
860| `new_string` | string | `"replacement text"` | Replacement text |
861| `replace_all` | boolean | `false` | Whether to replace all occurrences |
862
863##### Read
864
865Reads file contents.
866
867| Field | Type | Example | Description |
868| :---------- | :----- | :-------------------- | :----------------------------------------- |
869| `file_path` | string | `"/path/to/file.txt"` | Absolute path to the file to read |
870| `offset` | number | `10` | Optional line number to start reading from |
871| `limit` | number | `50` | Optional number of lines to read |
872
873##### Glob
874
875Finds files matching a glob pattern.
876
877| Field | Type | Example | Description |
878| :-------- | :----- | :--------------- | :--------------------------------------------------------------------- |
879| `pattern` | string | `"**/*.ts"` | Glob pattern to match files against |
880| `path` | string | `"/path/to/dir"` | Optional directory to search in. Defaults to current working directory |
881
882##### Grep
883
884Searches file contents with regular expressions.
885
886| Field | Type | Example | Description |
887| :------------ | :------ | :--------------- | :------------------------------------------------------------------------------------ |
888| `pattern` | string | `"TODO.*fix"` | Regular expression pattern to search for |
889| `path` | string | `"/path/to/dir"` | Optional file or directory to search in |
890| `glob` | string | `"*.ts"` | Optional glob pattern to filter files |
891| `output_mode` | string | `"content"` | `"content"`, `"files_with_matches"`, or `"count"`. Defaults to `"files_with_matches"` |
892| `-i` | boolean | `true` | Case insensitive search |
893| `multiline` | boolean | `false` | Enable multiline matching |
894
895##### WebFetch
896
897Fetches and processes web content.
898
899| Field | Type | Example | Description |
900| :------- | :----- | :---------------------------- | :----------------------------------- |
901| `url` | string | `"https://example.com/api"` | URL to fetch content from |
902| `prompt` | string | `"Extract the API endpoints"` | Prompt to run on the fetched content |
903
904##### WebSearch
905
906Searches the web.
907
908| Field | Type | Example | Description |
909| :---------------- | :----- | :----------------------------- | :------------------------------------------------ |
910| `query` | string | `"react hooks best practices"` | Search query |
911| `allowed_domains` | array | `["docs.example.com"]` | Optional: only include results from these domains |
912| `blocked_domains` | array | `["spam.example.com"]` | Optional: exclude results from these domains |
913
914##### Agent
915
916Spawns a [subagent](/en/sub-agents).
917
918| Field | Type | Example | Description |
919| :-------------- | :----- | :------------------------- | :------------------------------------------- |
920| `prompt` | string | `"Find all API endpoints"` | The task for the agent to perform |
921| `description` | string | `"Find API endpoints"` | Short description of the task |
922| `subagent_type` | string | `"Explore"` | Type of specialized agent to use |
923| `model` | string | `"sonnet"` | Optional model alias to override the default |
924
925#### PreToolUse decision control
926
927`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.
928
929| Field | Description |
930| :------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |
931| `permissionDecision` | `"allow"` bypasses the permission system, `"deny"` prevents the tool call, `"ask"` prompts the user to confirm |
932| `permissionDecisionReason` | For `"allow"` and `"ask"`, shown to the user but not Claude. For `"deny"`, shown to Claude |
933| `updatedInput` | Modifies the tool's input parameters before execution. Combine with `"allow"` to auto-approve, or `"ask"` to show the modified input to the user |
934| `additionalContext` | String added to Claude's context before the tool executes |
935
936When 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.
937
938```json theme={null}
939{
940 "hookSpecificOutput": {
941 "hookEventName": "PreToolUse",
942 "permissionDecision": "allow",
943 "permissionDecisionReason": "My reason here",
944 "updatedInput": {
945 "field_to_modify": "new value"
615 },946 },
616 "tool_use_id": "toolu_01ABC123..."947 "additionalContext": "Current environment: production. Proceed with caution."
948 }
617}949}
618```950```
619 951
620| Field | Type | Description |952<Note>
621| :------------ | :------ | :-------------------------------------------------- |953 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.
622| `file_path` | string | Absolute path to the file to edit |954</Note>
623| `old_string` | string | Text to find and replace |955
624| `new_string` | string | Replacement text |956### PermissionRequest
625| `replace_all` | boolean | Whether to replace all occurrences (default: false) |957
958Runs when the user is shown a permission dialog.
959Use [PermissionRequest decision control](#permissionrequest-decision-control) to allow or deny on behalf of the user.
960
961Matches on tool name, same values as PreToolUse.
962
963#### PermissionRequest input
626 964
627#### Read tool965PermissionRequest 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.
628 966
629```json theme={null}967```json theme={null}
630{968{
632 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",970 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
633 "cwd": "/Users/...",971 "cwd": "/Users/...",
634 "permission_mode": "default",972 "permission_mode": "default",
635 "hook_event_name": "PreToolUse",973 "hook_event_name": "PermissionRequest",
636 "tool_name": "Read",974 "tool_name": "Bash",
637 "tool_input": {975 "tool_input": {
638 "file_path": "/path/to/file.txt"976 "command": "rm -rf node_modules",
977 "description": "Remove node_modules directory"
639 },978 },
640 "tool_use_id": "toolu_01ABC123..."979 "permission_suggestions": [
980 {
981 "type": "addRules",
982 "rules": [{ "toolName": "Bash", "ruleContent": "rm -rf node_modules" }],
983 "behavior": "allow",
984 "destination": "localSettings"
985 }
986 ]
987}
988```
989
990#### PermissionRequest decision control
991
992`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:
993
994| Field | Description |
995| :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
996| `behavior` | `"allow"` grants the permission, `"deny"` denies it |
997| `updatedInput` | For `"allow"` only: modifies the tool's input parameters before execution |
998| `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 |
999| `message` | For `"deny"` only: tells Claude why the permission was denied |
1000| `interrupt` | For `"deny"` only: if `true`, stops Claude |
1001
1002```json theme={null}
1003{
1004 "hookSpecificOutput": {
1005 "hookEventName": "PermissionRequest",
1006 "decision": {
1007 "behavior": "allow",
1008 "updatedInput": {
1009 "command": "npm run lint"
1010 }
1011 }
1012 }
641}1013}
642```1014```
643 1015
644| Field | Type | Description |1016#### Permission update entries
645| :---------- | :----- | :----------------------------------------- |1017
646| `file_path` | string | Absolute path to the file to read |1018The `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.
647| `offset` | number | Optional line number to start reading from |1019
648| `limit` | number | Optional number of lines to read |1020| `type` | Fields | Effect |
1021| :------------------ | :--------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1022| `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"` |
1023| `replaceRules` | `rules`, `behavior`, `destination` | Replaces all rules of the given `behavior` at the `destination` with the provided `rules` |
1024| `removeRules` | `rules`, `behavior`, `destination` | Removes matching rules of the given `behavior` |
1025| `setMode` | `mode`, `destination` | Changes the permission mode. Valid modes are `default`, `acceptEdits`, `dontAsk`, `bypassPermissions`, and `plan` |
1026| `addDirectories` | `directories`, `destination` | Adds working directories. `directories` is an array of path strings |
1027| `removeDirectories` | `directories`, `destination` | Removes working directories |
1028
1029The `destination` field on every entry determines whether the change stays in memory or persists to a settings file.
649 1030
650### PostToolUse Input1031| `destination` | Writes to |
1032| :---------------- | :---------------------------------------------- |
1033| `session` | in-memory only, discarded when the session ends |
1034| `localSettings` | `.claude/settings.local.json` |
1035| `projectSettings` | `.claude/settings.json` |
1036| `userSettings` | `~/.claude/settings.json` |
651 1037
652The exact schema for `tool_input` and `tool_response` depends on the tool.1038A 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.
1039
1040### PostToolUse
1041
1042Runs immediately after a tool completes successfully.
1043
1044Matches on tool name, same values as PreToolUse.
1045
1046#### PostToolUse input
1047
1048`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.
653 1049
654```json theme={null}1050```json theme={null}
655{1051{
671}1067}
672```1068```
673 1069
674### Notification Input1070#### PostToolUse decision control
1071
1072`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:
1073
1074| Field | Description |
1075| :--------------------- | :----------------------------------------------------------------------------------------- |
1076| `decision` | `"block"` prompts Claude with the `reason`. Omit to allow the action to proceed |
1077| `reason` | Explanation shown to Claude when `decision` is `"block"` |
1078| `additionalContext` | Additional context for Claude to consider |
1079| `updatedMCPToolOutput` | For [MCP tools](#match-mcp-tools) only: replaces the tool's output with the provided value |
1080
1081```json theme={null}
1082{
1083 "decision": "block",
1084 "reason": "Explanation for decision",
1085 "hookSpecificOutput": {
1086 "hookEventName": "PostToolUse",
1087 "additionalContext": "Additional information for Claude"
1088 }
1089}
1090```
1091
1092### PostToolUseFailure
1093
1094Runs 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.
1095
1096Matches on tool name, same values as PreToolUse.
1097
1098#### PostToolUseFailure input
1099
1100PostToolUseFailure hooks receive the same `tool_name` and `tool_input` fields as PostToolUse, along with error information as top-level fields:
1101
1102```json theme={null}
1103{
1104 "session_id": "abc123",
1105 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1106 "cwd": "/Users/...",
1107 "permission_mode": "default",
1108 "hook_event_name": "PostToolUseFailure",
1109 "tool_name": "Bash",
1110 "tool_input": {
1111 "command": "npm test",
1112 "description": "Run test suite"
1113 },
1114 "tool_use_id": "toolu_01ABC123...",
1115 "error": "Command exited with non-zero status code 1",
1116 "is_interrupt": false
1117}
1118```
1119
1120| Field | Description |
1121| :------------- | :------------------------------------------------------------------------------ |
1122| `error` | String describing what went wrong |
1123| `is_interrupt` | Optional boolean indicating whether the failure was caused by user interruption |
1124
1125#### PostToolUseFailure decision control
1126
1127`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:
1128
1129| Field | Description |
1130| :------------------ | :------------------------------------------------------------ |
1131| `additionalContext` | Additional context for Claude to consider alongside the error |
1132
1133```json theme={null}
1134{
1135 "hookSpecificOutput": {
1136 "hookEventName": "PostToolUseFailure",
1137 "additionalContext": "Additional information about the failure for Claude"
1138 }
1139}
1140```
1141
1142### Notification
1143
1144Runs 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.
1145
1146Use 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:
1147
1148```json theme={null}
1149{
1150 "hooks": {
1151 "Notification": [
1152 {
1153 "matcher": "permission_prompt",
1154 "hooks": [
1155 {
1156 "type": "command",
1157 "command": "/path/to/permission-alert.sh"
1158 }
1159 ]
1160 },
1161 {
1162 "matcher": "idle_prompt",
1163 "hooks": [
1164 {
1165 "type": "command",
1166 "command": "/path/to/idle-notification.sh"
1167 }
1168 ]
1169 }
1170 ]
1171 }
1172}
1173```
1174
1175#### Notification input
1176
1177In 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.
675 1178
676```json theme={null}1179```json theme={null}
677{1180{
681 "permission_mode": "default",1184 "permission_mode": "default",
682 "hook_event_name": "Notification",1185 "hook_event_name": "Notification",
683 "message": "Claude needs your permission to use Bash",1186 "message": "Claude needs your permission to use Bash",
1187 "title": "Permission needed",
684 "notification_type": "permission_prompt"1188 "notification_type": "permission_prompt"
685}1189}
686```1190```
687 1191
688### UserPromptSubmit Input1192Notification 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:
1193
1194| Field | Description |
1195| :------------------ | :------------------------------- |
1196| `additionalContext` | String added to Claude's context |
1197
1198### SubagentStart
1199
1200Runs 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/`).
1201
1202#### SubagentStart input
1203
1204In 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).
1205
1206```json theme={null}
1207{
1208 "session_id": "abc123",
1209 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1210 "cwd": "/Users/...",
1211 "permission_mode": "default",
1212 "hook_event_name": "SubagentStart",
1213 "agent_id": "agent-abc123",
1214 "agent_type": "Explore"
1215}
1216```
1217
1218SubagentStart 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:
1219
1220| Field | Description |
1221| :------------------ | :------------------------------------- |
1222| `additionalContext` | String added to the subagent's context |
1223
1224```json theme={null}
1225{
1226 "hookSpecificOutput": {
1227 "hookEventName": "SubagentStart",
1228 "additionalContext": "Follow security guidelines for this task"
1229 }
1230}
1231```
1232
1233### SubagentStop
1234
1235Runs when a Claude Code subagent has finished responding. Matches on agent type, same values as SubagentStart.
1236
1237#### SubagentStop input
1238
1239In 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.
1240
1241```json theme={null}
1242{
1243 "session_id": "abc123",
1244 "transcript_path": "~/.claude/projects/.../abc123.jsonl",
1245 "cwd": "/Users/...",
1246 "permission_mode": "default",
1247 "hook_event_name": "SubagentStop",
1248 "stop_hook_active": false,
1249 "agent_id": "def456",
1250 "agent_type": "Explore",
1251 "agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl",
1252 "last_assistant_message": "Analysis complete. Found 3 potential issues..."
1253}
1254```
1255
1256SubagentStop hooks use the same decision control format as [Stop hooks](#stop-decision-control).
1257
1258### Stop
1259
1260Runs when the main Claude Code agent has finished responding. Does not run if
1261the stoppage occurred due to a user interrupt.
1262
1263#### Stop input
1264
1265In 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.
1266
1267```json theme={null}
1268{
1269 "session_id": "abc123",
1270 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1271 "cwd": "/Users/...",
1272 "permission_mode": "default",
1273 "hook_event_name": "Stop",
1274 "stop_hook_active": true,
1275 "last_assistant_message": "I've completed the refactoring. Here's a summary..."
1276}
1277```
1278
1279#### Stop decision control
1280
1281`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:
1282
1283| Field | Description |
1284| :--------- | :------------------------------------------------------------------------- |
1285| `decision` | `"block"` prevents Claude from stopping. Omit to allow Claude to stop |
1286| `reason` | Required when `decision` is `"block"`. Tells Claude why it should continue |
1287
1288```json theme={null}
1289{
1290 "decision": "block",
1291 "reason": "Must be provided when Claude is blocked from stopping"
1292}
1293```
1294
1295### TeammateIdle
1296
1297Runs 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.
1298
1299When 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.
1300
1301#### TeammateIdle input
1302
1303In addition to the [common input fields](#common-input-fields), TeammateIdle hooks receive `teammate_name` and `team_name`.
1304
1305```json theme={null}
1306{
1307 "session_id": "abc123",
1308 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1309 "cwd": "/Users/...",
1310 "permission_mode": "default",
1311 "hook_event_name": "TeammateIdle",
1312 "teammate_name": "researcher",
1313 "team_name": "my-project"
1314}
1315```
1316
1317| Field | Description |
1318| :-------------- | :-------------------------------------------- |
1319| `teammate_name` | Name of the teammate that is about to go idle |
1320| `team_name` | Name of the team |
1321
1322#### TeammateIdle decision control
1323
1324TeammateIdle hooks support two ways to control teammate behavior:
1325
1326* **Exit code 2**: the teammate receives the stderr message as feedback and continues working instead of going idle.
1327* **JSON `{"continue": false, "stopReason": "..."}`**: stops the teammate entirely, matching `Stop` hook behavior. The `stopReason` is shown to the user.
1328
1329This example checks that a build artifact exists before allowing a teammate to go idle:
1330
1331```bash theme={null}
1332#!/bin/bash
1333
1334if [ ! -f "./dist/output.js" ]; then
1335 echo "Build artifact missing. Run the build before stopping." >&2
1336 exit 2
1337fi
1338
1339exit 0
1340```
1341
1342### TaskCompleted
1343
1344Runs 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.
1345
1346When 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.
1347
1348#### TaskCompleted input
1349
1350In 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`.
1351
1352```json theme={null}
1353{
1354 "session_id": "abc123",
1355 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1356 "cwd": "/Users/...",
1357 "permission_mode": "default",
1358 "hook_event_name": "TaskCompleted",
1359 "task_id": "task-001",
1360 "task_subject": "Implement user authentication",
1361 "task_description": "Add login and signup endpoints",
1362 "teammate_name": "implementer",
1363 "team_name": "my-project"
1364}
1365```
1366
1367| Field | Description |
1368| :----------------- | :------------------------------------------------------ |
1369| `task_id` | Identifier of the task being completed |
1370| `task_subject` | Title of the task |
1371| `task_description` | Detailed description of the task. May be absent |
1372| `teammate_name` | Name of the teammate completing the task. May be absent |
1373| `team_name` | Name of the team. May be absent |
1374
1375#### TaskCompleted decision control
1376
1377TaskCompleted hooks support two ways to control task completion:
1378
1379* **Exit code 2**: the task is not marked as completed and the stderr message is fed back to the model as feedback.
1380* **JSON `{"continue": false, "stopReason": "..."}`**: stops the teammate entirely, matching `Stop` hook behavior. The `stopReason` is shown to the user.
1381
1382This example runs tests and blocks task completion if they fail:
1383
1384```bash theme={null}
1385#!/bin/bash
1386INPUT=$(cat)
1387TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')
1388
1389# Run the test suite
1390if ! npm test 2>&1; then
1391 echo "Tests not passing. Fix failing tests before completing: $TASK_SUBJECT" >&2
1392 exit 2
1393fi
1394
1395exit 0
1396```
1397
1398### ConfigChange
1399
1400Runs when a configuration file changes during a session. Use this to audit settings changes, enforce security policies, or block unauthorized modifications to configuration files.
1401
1402ConfigChange 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.
1403
1404The matcher filters on the configuration source:
1405
1406| Matcher | When it fires |
1407| :----------------- | :---------------------------------------- |
1408| `user_settings` | `~/.claude/settings.json` changes |
1409| `project_settings` | `.claude/settings.json` changes |
1410| `local_settings` | `.claude/settings.local.json` changes |
1411| `policy_settings` | Managed policy settings change |
1412| `skills` | A skill file in `.claude/skills/` changes |
1413
1414This example logs all configuration changes for security auditing:
1415
1416```json theme={null}
1417{
1418 "hooks": {
1419 "ConfigChange": [
1420 {
1421 "hooks": [
1422 {
1423 "type": "command",
1424 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/audit-config-change.sh"
1425 }
1426 ]
1427 }
1428 ]
1429 }
1430}
1431```
1432
1433#### ConfigChange input
1434
1435In 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.
1436
1437```json theme={null}
1438{
1439 "session_id": "abc123",
1440 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1441 "cwd": "/Users/...",
1442 "permission_mode": "default",
1443 "hook_event_name": "ConfigChange",
1444 "source": "project_settings",
1445 "file_path": "/Users/.../my-project/.claude/settings.json"
1446}
1447```
1448
1449#### ConfigChange decision control
1450
1451ConfigChange 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.
1452
1453| Field | Description |
1454| :--------- | :--------------------------------------------------------------------------------------- |
1455| `decision` | `"block"` prevents the configuration change from being applied. Omit to allow the change |
1456| `reason` | Explanation shown to the user when `decision` is `"block"` |
1457
1458```json theme={null}
1459{
1460 "decision": "block",
1461 "reason": "Configuration changes to project settings require admin approval"
1462}
1463```
1464
1465`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.
1466
1467### WorktreeCreate
1468
1469When 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.
1470
1471The hook must print the absolute path to the created worktree directory on stdout. Claude Code uses this path as the working directory for the isolated session.
1472
1473This example creates an SVN working copy and prints the path for Claude Code to use. Replace the repository URL with your own:
1474
1475```json theme={null}
1476{
1477 "hooks": {
1478 "WorktreeCreate": [
1479 {
1480 "hooks": [
1481 {
1482 "type": "command",
1483 "command": "bash -c 'NAME=$(jq -r .name); DIR=\"$HOME/.claude/worktrees/$NAME\"; svn checkout https://svn.example.com/repo/trunk \"$DIR\" >&2 && echo \"$DIR\"'"
1484 }
1485 ]
1486 }
1487 ]
1488 }
1489}
1490```
1491
1492The 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.
1493
1494#### WorktreeCreate input
1495
1496In 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`).
689 1497
690```json theme={null}1498```json theme={null}
691{1499{
692 "session_id": "abc123",1500 "session_id": "abc123",
693 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1501 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
694 "cwd": "/Users/...",1502 "cwd": "/Users/...",
695 "permission_mode": "default",1503 "hook_event_name": "WorktreeCreate",
696 "hook_event_name": "UserPromptSubmit",1504 "name": "feature-auth"
697 "prompt": "Write a function to calculate the factorial of a number"
698}1505}
699```1506```
700 1507
701### Stop Input1508#### WorktreeCreate output
1509
1510The hook must print the absolute path to the created worktree directory on stdout. If the hook fails or produces no output, worktree creation fails with an error.
1511
1512WorktreeCreate hooks do not use the standard allow/block decision model. Instead, the hook's success or failure determines the outcome. Only `type: "command"` hooks are supported.
702 1513
703`stop_hook_active` is true when Claude Code is already continuing as a result of1514### WorktreeRemove
704a stop hook. Check this value or process the transcript to prevent Claude Code1515
705from running indefinitely.1516The 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.
1517
1518Claude Code passes the path that WorktreeCreate printed on stdout as `worktree_path` in the hook input. This example reads that path and removes the directory:
706 1519
707```json theme={null}1520```json theme={null}
708{1521{
709 "session_id": "abc123",1522 "hooks": {
710 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1523 "WorktreeRemove": [
711 "cwd": "/Users/...",1524 {
712 "permission_mode": "default",1525 "hooks": [
713 "hook_event_name": "Stop",1526 {
714 "stop_hook_active": true1527 "type": "command",
1528 "command": "bash -c 'jq -r .worktree_path | xargs rm -rf'"
1529 }
1530 ]
1531 }
1532 ]
1533 }
715}1534}
716```1535```
717 1536
718### SubagentStop Input1537#### WorktreeRemove input
719 1538
720Triggered when a subagent finishes. 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.1539In 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.
721 1540
722```json theme={null}1541```json theme={null}
723{1542{
724 "session_id": "abc123",1543 "session_id": "abc123",
725 "transcript_path": "~/.claude/projects/.../abc123.jsonl",1544 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
726 "cwd": "/Users/...",1545 "cwd": "/Users/...",
727 "permission_mode": "default",1546 "hook_event_name": "WorktreeRemove",
728 "hook_event_name": "SubagentStop",1547 "worktree_path": "/Users/.../my-project/.claude/worktrees/feature-auth"
729 "stop_hook_active": false,
730 "agent_id": "def456",
731 "agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl"
732}1548}
733```1549```
734 1550
735### PreCompact Input1551WorktreeRemove 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. Only `type: "command"` hooks are supported.
1552
1553### PreCompact
1554
1555Runs before Claude Code is about to run a compact operation.
1556
1557The matcher value indicates whether compaction was triggered manually or automatically:
736 1558
737For `manual`, `custom_instructions` comes from what the user passes into1559| Matcher | When it fires |
738`/compact`. For `auto`, `custom_instructions` is empty.1560| :------- | :------------------------------------------- |
1561| `manual` | `/compact` |
1562| `auto` | Auto-compact when the context window is full |
1563
1564#### PreCompact input
1565
1566In 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.
739 1567
740```json theme={null}1568```json theme={null}
741{1569{
742 "session_id": "abc123",1570 "session_id": "abc123",
743 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1571 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1572 "cwd": "/Users/...",
744 "permission_mode": "default",1573 "permission_mode": "default",
745 "hook_event_name": "PreCompact",1574 "hook_event_name": "PreCompact",
746 "trigger": "manual",1575 "trigger": "manual",
748}1577}
749```1578```
750 1579
751### Setup Input1580### PostCompact
752 1581
753```json theme={null}1582Runs 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.
754{1583
755 "session_id": "abc123",1584The same matcher values apply as for `PreCompact`:
756 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
757 "cwd": "/Users/...",
758 "permission_mode": "default",
759 "hook_event_name": "Setup",
760 "trigger": "init"
761}
762```
763 1585
764The `trigger` field will be either `"init"` (from `--init` or `--init-only`) or `"maintenance"` (from `--maintenance`).1586| Matcher | When it fires |
1587| :------- | :------------------------------------------------- |
1588| `manual` | After `/compact` |
1589| `auto` | After auto-compact when the context window is full |
765 1590
766### SessionStart Input1591#### PostCompact input
1592
1593In 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.
767 1594
768```json theme={null}1595```json theme={null}
769{1596{
770 "session_id": "abc123",1597 "session_id": "abc123",
771 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1598 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
772 "cwd": "/Users/...",1599 "cwd": "/Users/...",
773 "permission_mode": "default",1600 "permission_mode": "default",
774 "hook_event_name": "SessionStart",1601 "hook_event_name": "PostCompact",
775 "source": "startup",1602 "trigger": "manual",
776 "model": "claude-sonnet-4-20250514"1603 "compact_summary": "Summary of the compacted conversation..."
777}1604}
778```1605```
779 1606
780The `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 when available. If you start Claude Code with `claude --agent <name>`, an `agent_type` field contains the agent name.1607PostCompact hooks have no decision control. They cannot affect the compaction result but can perform follow-up tasks.
781 1608
782### SubagentStart Input1609### SessionEnd
783 1610
784```json theme={null}1611Runs when a Claude Code session ends. Useful for cleanup tasks, logging session
785{1612statistics, or saving session state. Supports matchers to filter by exit reason.
786 "session_id": "abc123",1613
787 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1614The `reason` field in the hook input indicates why the session ended:
788 "cwd": "/Users/...",
789 "permission_mode": "default",
790 "hook_event_name": "SubagentStart",
791 "agent_id": "agent-abc123",
792 "agent_type": "Explore"
793}
794```
795 1615
796Triggered when a subagent is spawned. The `agent_id` field contains the unique identifier for the subagent, and `agent_type` contains the agent name (built-in agents like `"Bash"`, `"Explore"`, `"Plan"`, or custom agent names).1616| Reason | Description |
1617| :---------------------------- | :----------------------------------------- |
1618| `clear` | Session cleared with `/clear` command |
1619| `logout` | User logged out |
1620| `prompt_input_exit` | User exited while prompt input was visible |
1621| `bypass_permissions_disabled` | Bypass permissions mode was disabled |
1622| `other` | Other exit reasons |
797 1623
798### SessionEnd Input1624#### SessionEnd input
1625
1626In 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.
799 1627
800```json theme={null}1628```json theme={null}
801{1629{
802 "session_id": "abc123",1630 "session_id": "abc123",
803 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1631 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
804 "cwd": "/Users/...",1632 "cwd": "/Users/...",
805 "permission_mode": "default",1633 "permission_mode": "default",
806 "hook_event_name": "SessionEnd",1634 "hook_event_name": "SessionEnd",
807 "reason": "exit"1635 "reason": "other"
808}1636}
809```1637```
810 1638
811## Hook Output1639SessionEnd hooks have no decision control. They cannot block session termination but can perform cleanup tasks.
812
813There are two mutually exclusive ways for hooks to return output back to Claude Code. The output
814communicates whether to block and any feedback that should be shown to Claude
815and the user.
816
817### Simple: Exit Code
818
819Hooks communicate status through exit codes, stdout, and stderr:
820 1640
821* **Exit code 0**: Success. `stdout` is shown to the user in verbose mode1641SessionEnd hooks have a default timeout of 1.5 seconds. This applies to both session exit and `/clear`. 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.
822 (ctrl+o), except for `UserPromptSubmit` and `SessionStart`, where stdout is
823 added to the context. JSON output in `stdout` is parsed for structured control
824 (see [Advanced: JSON Output](#advanced-json-output)).
825* **Exit code 2**: Blocking error. Only `stderr` is used as the error message
826 and fed back to Claude. The format is `[command]: {stderr}`. JSON in `stdout`
827 is **not** processed for exit code 2. See per-hook-event behavior below.
828* **Other exit codes**: Non-blocking error. `stderr` is shown to the user in verbose mode (ctrl+o) with
829 format `Failed with non-blocking status code: {stderr}`. If `stderr` is empty,
830 it shows `No stderr output`. Execution continues.
831 1642
832<Warning>1643```bash theme={null}
833 Reminder: Claude Code does not see stdout if the exit code is 0, except for1644CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS=5000 claude
834 the `UserPromptSubmit` hook where stdout is injected as context.1645```
835</Warning>
836
837#### Exit Code 2 Behavior
838 1646
839| Hook Event | Behavior |1647### Elicitation
840| ------------------- | ------------------------------------------------------------------ |
841| `PreToolUse` | Blocks the tool call, shows stderr to Claude |
842| `PermissionRequest` | Denies the permission, shows stderr to Claude |
843| `PostToolUse` | Shows stderr to Claude (tool already ran) |
844| `Notification` | N/A, shows stderr to user only |
845| `UserPromptSubmit` | Blocks prompt processing, erases prompt, shows stderr to user only |
846| `Stop` | Blocks stoppage, shows stderr to Claude |
847| `SubagentStop` | Blocks stoppage, shows stderr to Claude subagent |
848| `PreCompact` | N/A, shows stderr to user only |
849| `Setup` | N/A, shows stderr to user only |
850| `SessionStart` | N/A, shows stderr to user only |
851| `SessionEnd` | N/A, shows stderr to user only |
852 1648
853### Advanced: JSON Output1649Runs 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.
854 1650
855Hooks can return structured JSON in `stdout` for more sophisticated control.1651The matcher field matches against the MCP server name.
856 1652
857<Warning>1653#### Elicitation input
858 JSON output is only processed when the hook exits with code 0. If your hook
859 exits with code 2 (blocking error), `stderr` text is used directly—any JSON in `stdout`
860 is ignored. For other non-zero exit codes, only `stderr` is shown to the user in verbose mode (ctrl+o).
861</Warning>
862 1654
863#### Common JSON Fields1655In 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.
864 1656
865All hook types can include these optional fields:1657For form-mode elicitation (the most common case):
866 1658
867```json theme={null}1659```json theme={null}
868{1660{
869 "continue": true, // Whether Claude should continue after hook execution (default: true)1661 "session_id": "abc123",
870 "stopReason": "string", // Message shown when continue is false1662 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
871 1663 "cwd": "/Users/...",
872 "suppressOutput": true, // Hide stdout from transcript mode (default: false)1664 "permission_mode": "default",
873 "systemMessage": "string" // Optional warning message shown to the user1665 "hook_event_name": "Elicitation",
1666 "mcp_server_name": "my-mcp-server",
1667 "message": "Please provide your credentials",
1668 "mode": "form",
1669 "requested_schema": {
1670 "type": "object",
1671 "properties": {
1672 "username": { "type": "string", "title": "Username" }
1673 }
1674 }
874}1675}
875```1676```
876 1677
877If `continue` is false, Claude stops processing after the hooks run.1678For URL-mode elicitation (browser-based authentication):
878
879* For `PreToolUse`, this is different from `"permissionDecision": "deny"`, which
880 only blocks a specific tool call and provides automatic feedback to Claude.
881* For `PostToolUse`, this is different from `"decision": "block"`, which
882 provides automated feedback to Claude.
883* For `UserPromptSubmit`, this prevents the prompt from being processed.
884* For `Stop` and `SubagentStop`, this takes precedence over any
885 `"decision": "block"` output.
886* In all cases, `"continue" = false` takes precedence over any
887 `"decision": "block"` output.
888
889`stopReason` accompanies `continue` with a reason shown to the user, not shown
890to Claude.
891
892#### `PreToolUse` Decision Control
893
894`PreToolUse` hooks can control whether a tool call proceeds.
895
896* `"allow"` bypasses the permission system. `permissionDecisionReason` is shown
897 to the user but not to Claude.
898* `"deny"` prevents the tool call from executing. `permissionDecisionReason` is
899 shown to Claude.
900* `"ask"` asks the user to confirm the tool call in the UI.
901 `permissionDecisionReason` is shown to the user but not to Claude.
902
903Additionally, hooks can modify tool inputs before execution using `updatedInput`:
904 1679
905* `updatedInput` modifies the tool's input parameters before the tool executes1680```json theme={null}
906* Combine with `"permissionDecision": "allow"` to modify the input and auto-approve the tool call1681{
907* Combine with `"permissionDecision": "ask"` to modify the input and show it to the user for confirmation1682 "session_id": "abc123",
1683 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1684 "cwd": "/Users/...",
1685 "permission_mode": "default",
1686 "hook_event_name": "Elicitation",
1687 "mcp_server_name": "my-mcp-server",
1688 "message": "Please authenticate",
1689 "mode": "url",
1690 "url": "https://auth.example.com/login"
1691}
1692```
908 1693
909Hooks can also provide context to Claude using `additionalContext`:1694#### Elicitation output
910 1695
911* `"hookSpecificOutput.additionalContext"` adds a string to Claude's context before the tool executes.1696To respond programmatically without showing the dialog, return a JSON object with `hookSpecificOutput`:
912 1697
913```json theme={null}1698```json theme={null}
914{1699{
915 "hookSpecificOutput": {1700 "hookSpecificOutput": {
916 "hookEventName": "PreToolUse",1701 "hookEventName": "Elicitation",
917 "permissionDecision": "allow",1702 "action": "accept",
918 "permissionDecisionReason": "My reason here",1703 "content": {
919 "updatedInput": {1704 "username": "alice"
920 "field_to_modify": "new value"1705 }
921 },
922 "additionalContext": "Current environment: production. Proceed with caution."
923 }1706 }
924}1707}
925```1708```
926 1709
927<Note>1710| Field | Values | Description |
928 The `decision` and `reason` fields are deprecated for PreToolUse hooks.1711| :-------- | :---------------------------- | :--------------------------------------------------------------- |
929 Use `hookSpecificOutput.permissionDecision` and1712| `action` | `accept`, `decline`, `cancel` | Whether to accept, decline, or cancel the request |
930 `hookSpecificOutput.permissionDecisionReason` instead. The deprecated fields1713| `content` | object | Form field values to submit. Only used when `action` is `accept` |
931 `"approve"` and `"block"` map to `"allow"` and `"deny"` respectively.1714
932</Note>1715Exit code 2 denies the elicitation and shows stderr to the user.
1716
1717### ElicitationResult
933 1718
934#### `PermissionRequest` Decision Control1719Runs 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.
935 1720
936`PermissionRequest` hooks can allow or deny permission requests shown to the user.1721The matcher field matches against the MCP server name.
937 1722
938* For `"behavior": "allow"` you can also optionally pass in an `"updatedInput"` that modifies the tool's input parameters before the tool executes.1723#### ElicitationResult input
939* 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.1724
1725In addition to the [common input fields](#common-input-fields), ElicitationResult hooks receive `mcp_server_name`, `action`, and optional `mode`, `elicitation_id`, and `content` fields.
940 1726
941```json theme={null}1727```json theme={null}
942{1728{
943 "hookSpecificOutput": {1729 "session_id": "abc123",
944 "hookEventName": "PermissionRequest",1730 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
945 "decision": {1731 "cwd": "/Users/...",
946 "behavior": "allow",1732 "permission_mode": "default",
947 "updatedInput": {1733 "hook_event_name": "ElicitationResult",
948 "command": "npm run lint"1734 "mcp_server_name": "my-mcp-server",
949 }1735 "action": "accept",
950 }1736 "content": { "username": "alice" },
951 }1737 "mode": "form",
1738 "elicitation_id": "elicit-123"
952}1739}
953```1740```
954 1741
955#### `PostToolUse` Decision Control1742#### ElicitationResult output
956 1743
957`PostToolUse` hooks can provide feedback to Claude after tool execution.1744To override the user's response, return a JSON object with `hookSpecificOutput`:
958
959* `"block"` automatically prompts Claude with `reason`.
960* `undefined` does nothing. `reason` is ignored.
961* `"hookSpecificOutput.additionalContext"` adds context for Claude to consider.
962 1745
963```json theme={null}1746```json theme={null}
964{1747{
965 "decision": "block" | undefined,
966 "reason": "Explanation for decision",
967 "hookSpecificOutput": {1748 "hookSpecificOutput": {
968 "hookEventName": "PostToolUse",1749 "hookEventName": "ElicitationResult",
969 "additionalContext": "Additional information for Claude"1750 "action": "decline",
1751 "content": {}
970 }1752 }
971}1753}
972```1754```
973 1755
974#### `UserPromptSubmit` Decision Control1756| Field | Values | Description |
1757| :-------- | :---------------------------- | :--------------------------------------------------------------------- |
1758| `action` | `accept`, `decline`, `cancel` | Overrides the user's action |
1759| `content` | object | Overrides form field values. Only meaningful when `action` is `accept` |
975 1760
976`UserPromptSubmit` hooks can control whether a user prompt is processed and add context.1761Exit code 2 blocks the response, changing the effective action to `decline`.
977 1762
978**Adding context (exit code 0):**1763## Prompt-based hooks
979There are two ways to add context to the conversation:
980 1764
9811. **Plain text stdout** (simpler): Any non-JSON text written to stdout is added1765In 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.
982 as context. This is the easiest way to inject information.
983 1766
9842. **JSON with `additionalContext`** (structured): Use the JSON format below for1767Events that support all four hook types (`command`, `http`, `prompt`, and `agent`):
985 more control. The `additionalContext` field is added as context.
986 1768
987Both methods work with exit code 0. Plain stdout is shown as hook output in1769* `PermissionRequest`
988the transcript; `additionalContext` is added more discretely.1770* `PostToolUse`
1771* `PostToolUseFailure`
1772* `PreToolUse`
1773* `Stop`
1774* `SubagentStop`
1775* `TaskCompleted`
1776* `UserPromptSubmit`
989 1777
990**Blocking prompts:**1778Events that only support `type: "command"` hooks:
991 1779
992* `"decision": "block"` prevents the prompt from being processed. The submitted1780* `ConfigChange`
993 prompt is erased from context. `"reason"` is shown to the user but not added1781* `Elicitation`
994 to context.1782* `ElicitationResult`
995* `"decision": undefined` (or omitted) allows the prompt to proceed normally.1783* `InstructionsLoaded`
1784* `Notification`
1785* `PostCompact`
1786* `PreCompact`
1787* `SessionEnd`
1788* `SessionStart`
1789* `SubagentStart`
1790* `TeammateIdle`
1791* `WorktreeCreate`
1792* `WorktreeRemove`
996 1793
997```json theme={null}1794### How prompt-based hooks work
998{
999 "decision": "block" | undefined,
1000 "reason": "Explanation for decision",
1001 "hookSpecificOutput": {
1002 "hookEventName": "UserPromptSubmit",
1003 "additionalContext": "My additional context here"
1004 }
1005}
1006```
1007 1795
1008<Note>1796Instead of executing a Bash command, prompt-based hooks:
1009 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
1010 block prompts or want more structured control.
1011</Note>
1012 1797
1013#### `Stop`/`SubagentStop` Decision Control17981. Send the hook input and your prompt to a Claude model, Haiku by default
17992. The LLM responds with structured JSON containing a decision
18003. Claude Code processes the decision automatically
1014 1801
1015`Stop` and `SubagentStop` hooks can control whether Claude must continue.1802### Prompt hook configuration
1016 1803
1017* `"block"` prevents Claude from stopping. You must populate `reason` for Claude1804Set `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.
1018 to know how to proceed.1805
1019* `undefined` allows Claude to stop. `reason` is ignored.1806This `Stop` hook asks the LLM to evaluate whether all tasks are complete before allowing Claude to finish:
1020 1807
1021```json theme={null}1808```json theme={null}
1022{1809{
1023 "decision": "block" | undefined,1810 "hooks": {
1024 "reason": "Must be provided when Claude is blocked from stopping"1811 "Stop": [
1812 {
1813 "hooks": [
1814 {
1815 "type": "prompt",
1816 "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."
1817 }
1818 ]
1819 }
1820 ]
1821 }
1025}1822}
1026```1823```
1027 1824
1028#### `Setup` Decision Control1825| Field | Required | Description |
1826| :-------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
1827| `type` | yes | Must be `"prompt"` |
1828| `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 |
1829| `model` | no | Model to use for evaluation. Defaults to a fast model |
1830| `timeout` | no | Timeout in seconds. Default: 30 |
1029 1831
1030`Setup` hooks allow you to load context and configure the environment during repository initialization or maintenance.1832### Response schema
1031 1833
1032* `"hookSpecificOutput.additionalContext"` adds the string to the context.1834The LLM must respond with JSON containing:
1033* Multiple hooks' `additionalContext` values are concatenated.
1034* Setup hooks have access to `CLAUDE_ENV_FILE` for persisting environment variables.
1035 1835
1036```json theme={null}1836```json theme={null}
1037{1837{
1038 "hookSpecificOutput": {1838 "ok": true | false,
1039 "hookEventName": "Setup",1839 "reason": "Explanation for the decision"
1040 "additionalContext": "Repository initialized with custom configuration"
1041 }
1042}1840}
1043```1841```
1044 1842
1045#### `SessionStart` Decision Control1843| Field | Description |
1844| :------- | :--------------------------------------------------------- |
1845| `ok` | `true` allows the action, `false` prevents it |
1846| `reason` | Required when `ok` is `false`. Explanation shown to Claude |
1046 1847
1047`SessionStart` hooks allow you to load in context at the start of a session.1848### Example: Multi-criteria Stop hook
1048 1849
1049* `"hookSpecificOutput.additionalContext"` adds the string to the context.1850This `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:
1050* Multiple hooks' `additionalContext` values are concatenated.
1051 1851
1052```json theme={null}1852```json theme={null}
1053{1853{
1054 "hookSpecificOutput": {1854 "hooks": {
1055 "hookEventName": "SessionStart",1855 "Stop": [
1056 "additionalContext": "My additional context here"1856 {
1857 "hooks": [
1858 {
1859 "type": "prompt",
1860 "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.",
1861 "timeout": 30
1862 }
1863 ]
1864 }
1865 ]
1057 }1866 }
1058}1867}
1059```1868```
1060 1869
1061#### `SessionEnd` Decision Control1870## Agent-based hooks
1062
1063`SessionEnd` hooks run when a session ends. They cannot block session termination
1064but can perform cleanup tasks.
1065
1066#### Exit Code Example: Bash Command Validation
1067
1068```python theme={null}
1069#!/usr/bin/env python3
1070import json
1071import re
1072import sys
1073
1074# Define validation rules as a list of (regex pattern, message) tuples
1075VALIDATION_RULES = [
1076 (
1077 r"\bgrep\b(?!.*\|)",
1078 "Use 'rg' (ripgrep) instead of 'grep' for better performance and features",
1079 ),
1080 (
1081 r"\bfind\s+\S+\s+-name\b",
1082 "Use 'rg --files | rg pattern' or 'rg --files -g pattern' instead of 'find -name' for better performance",
1083 ),
1084]
1085
1086
1087def validate_command(command: str) -> list[str]:
1088 issues = []
1089 for pattern, message in VALIDATION_RULES:
1090 if re.search(pattern, command):
1091 issues.append(message)
1092 return issues
1093
1094
1095try:
1096 input_data = json.load(sys.stdin)
1097except json.JSONDecodeError as e:
1098 print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
1099 sys.exit(1)
1100
1101tool_name = input_data.get("tool_name", "")
1102tool_input = input_data.get("tool_input", {})
1103command = tool_input.get("command", "")
1104
1105if tool_name != "Bash" or not command:
1106 sys.exit(1)
1107
1108# Validate the command
1109issues = validate_command(command)
1110
1111if issues:
1112 for message in issues:
1113 print(f"• {message}", file=sys.stderr)
1114 # Exit code 2 blocks tool call and shows stderr to Claude
1115 sys.exit(2)
1116```
1117 1871
1118#### JSON Output Example: UserPromptSubmit to Add Context and Validation1872Agent-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.
1119 1873
1120<Note>1874### How agent hooks work
1121 For `UserPromptSubmit` hooks, you can inject context using either method:
1122 1875
1123 * **Plain text stdout** with exit code 0: Simplest approach, prints text1876When an agent hook fires:
1124 * **JSON output** with exit code 0: Use `"decision": "block"` to reject prompts,
1125 or `additionalContext` for structured context injection
1126 1877
1127 Remember: Exit code 2 only uses `stderr` for the error message. To block using18781. Claude Code spawns a subagent with your prompt and the hook's JSON input
1128 JSON (with a custom reason), use `"decision": "block"` with exit code 0.18792. The subagent can use tools like Read, Grep, and Glob to investigate
1129</Note>18803. After up to 50 turns, the subagent returns a structured `{ "ok": true/false }` decision
18814. Claude Code processes the decision the same way as a prompt hook
1130 1882
1131```python theme={null}1883Agent hooks are useful when verification requires inspecting actual files or test output, not just evaluating the hook input data alone.
1132#!/usr/bin/env python3
1133import json
1134import sys
1135import re
1136import datetime
1137
1138# Load input from stdin
1139try:
1140 input_data = json.load(sys.stdin)
1141except json.JSONDecodeError as e:
1142 print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
1143 sys.exit(1)
1144
1145prompt = input_data.get("prompt", "")
1146
1147# Check for sensitive patterns
1148sensitive_patterns = [
1149 (r"(?i)\b(password|secret|key|token)\s*[:=]", "Prompt contains potential secrets"),
1150]
1151
1152for pattern, message in sensitive_patterns:
1153 if re.search(pattern, prompt):
1154 # Use JSON output to block with a specific reason
1155 output = {
1156 "decision": "block",
1157 "reason": f"Security policy violation: {message}. Please rephrase your request without sensitive information."
1158 }
1159 print(json.dumps(output))
1160 sys.exit(0)
1161 1884
1162# Add current time to context1885### Agent hook configuration
1163context = f"Current time: {datetime.datetime.now()}"
1164print(context)
1165 1886
1166"""1887Set `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:
1167The following is also equivalent:
1168print(json.dumps({
1169 "hookSpecificOutput": {
1170 "hookEventName": "UserPromptSubmit",
1171 "additionalContext": context,
1172 },
1173}))
1174"""
1175 1888
1176# Allow the prompt to proceed with the additional context1889| Field | Required | Description |
1177sys.exit(0)1890| :-------- | :------- | :------------------------------------------------------------------------------------------ |
1178```1891| `type` | yes | Must be `"agent"` |
1892| `prompt` | yes | Prompt describing what to verify. Use `$ARGUMENTS` as a placeholder for the hook input JSON |
1893| `model` | no | Model to use. Defaults to a fast model |
1894| `timeout` | no | Timeout in seconds. Default: 60 |
1179 1895
1180#### JSON Output Example: PreToolUse with Approval1896The response schema is the same as prompt hooks: `{ "ok": true }` to allow or `{ "ok": false, "reason": "..." }` to block.
1181
1182```python theme={null}
1183#!/usr/bin/env python3
1184import json
1185import sys
1186
1187# Load input from stdin
1188try:
1189 input_data = json.load(sys.stdin)
1190except json.JSONDecodeError as e:
1191 print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
1192 sys.exit(1)
1193
1194tool_name = input_data.get("tool_name", "")
1195tool_input = input_data.get("tool_input", {})
1196
1197# Example: Auto-approve file reads for documentation files
1198if tool_name == "Read":
1199 file_path = tool_input.get("file_path", "")
1200 if file_path.endswith((".md", ".mdx", ".txt", ".json")):
1201 # Use JSON output to auto-approve the tool call
1202 output = {
1203 "decision": "approve",
1204 "reason": "Documentation file auto-approved",
1205 "suppressOutput": True # Don't show in verbose mode
1206 }
1207 print(json.dumps(output))
1208 sys.exit(0)
1209
1210# For other cases, let the normal permission flow proceed
1211sys.exit(0)
1212```
1213 1897
1214## Working with MCP Tools1898This `Stop` hook verifies that all unit tests pass before allowing Claude to finish:
1215 1899
1216Claude Code hooks work seamlessly with1900```json theme={null}
1217[Model Context Protocol (MCP) tools](/en/mcp). When MCP servers1901{
1218provide tools, they appear with a special naming pattern that you can match in1902 "hooks": {
1219your hooks.1903 "Stop": [
1904 {
1905 "hooks": [
1906 {
1907 "type": "agent",
1908 "prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
1909 "timeout": 120
1910 }
1911 ]
1912 }
1913 ]
1914 }
1915}
1916```
1220 1917
1221### MCP Tool Naming1918## Run hooks in the background
1222 1919
1223MCP tools follow the pattern `mcp__<server>__<tool>`, for example:1920By 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.
1224 1921
1225* `mcp__memory__create_entities` - Memory server's create entities tool1922### Configure an async hook
1226* `mcp__filesystem__read_file` - Filesystem server's read file tool
1227* `mcp__github__search_repositories` - GitHub server's search tool
1228 1923
1229### Configuring Hooks for MCP Tools1924Add `"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.
1230 1925
1231You can target specific MCP tools or entire MCP servers:1926This 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:
1232 1927
1233```json theme={null}1928```json theme={null}
1234{1929{
1235 "hooks": {1930 "hooks": {
1236 "PreToolUse": [1931 "PostToolUse": [
1237 {
1238 "matcher": "mcp__memory__.*",
1239 "hooks": [
1240 {
1241 "type": "command",
1242 "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
1243 }
1244 ]
1245 },
1246 {1932 {
1247 "matcher": "mcp__.*__write.*",1933 "matcher": "Write",
1248 "hooks": [1934 "hooks": [
1249 {1935 {
1250 "type": "command",1936 "type": "command",
1251 "command": "/home/user/scripts/validate-mcp-write.py"1937 "command": "/path/to/run-tests.sh",
1938 "async": true,
1939 "timeout": 120
1252 }1940 }
1253 ]1941 ]
1254 }1942 }
1257}1945}
1258```1946```
1259 1947
1260## Examples1948The `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.
1261
1262<Tip>
1263 For practical examples including code formatting, notifications, and file protection, see [More Examples](/en/hooks-guide#more-examples) in the get started guide.
1264</Tip>
1265 1949
1266## Security Considerations1950### How async hooks execute
1267 1951
1268### Disclaimer1952When 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.
1269 1953
1270**USE AT YOUR OWN RISK**: Claude Code hooks execute arbitrary shell commands on1954After 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.
1271your system automatically. By using hooks, you acknowledge that:
1272 1955
1273* You are solely responsible for the commands you configure1956Async hook completion notifications are suppressed by default. To see them, enable verbose mode with `Ctrl+O` or start Claude Code with `--verbose`.
1274* Hooks can modify, delete, or access any files your user account can access
1275* Malicious or poorly written hooks can cause data loss or system damage
1276* Anthropic provides no warranty and assumes no liability for any damages
1277 resulting from hook usage
1278* You should thoroughly test hooks in a safe environment before production use
1279 1957
1280Always review and understand any hook commands before adding them to your1958### Example: run tests after file changes
1281configuration.
1282 1959
1283### Security Best Practices1960This 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`:
1284 1961
1285Here are some key practices for writing more secure hooks:1962```bash theme={null}
1963#!/bin/bash
1964# run-tests-async.sh
1286 1965
12871. **Validate and sanitize inputs** - Never trust input data blindly1966# Read hook input from stdin
12882. **Always quote shell variables** - Use `"$VAR"` not `$VAR`1967INPUT=$(cat)
12893. **Block path traversal** - Check for `..` in file paths1968FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
12904. **Use absolute paths** - Specify full paths for scripts (use
1291 "\$CLAUDE\_PROJECT\_DIR" for the project path)
12925. **Skip sensitive files** - Avoid `.env`, `.git/`, keys, etc.
1293 1969
1294### Configuration Safety1970# Only run tests for source files
1971if [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.js ]]; then
1972 exit 0
1973fi
1295 1974
1296Direct edits to hooks in settings files don't take effect immediately. Claude1975# Run tests and report results via systemMessage
1297Code:1976RESULT=$(npm test 2>&1)
1977EXIT_CODE=$?
1298 1978
12991. Captures a snapshot of hooks at startup1979if [ $EXIT_CODE -eq 0 ]; then
13002. Uses this snapshot throughout the session1980 echo "{\"systemMessage\": \"Tests passed after editing $FILE_PATH\"}"
13013. Warns if hooks are modified externally1981else
13024. Requires review in `/hooks` menu for changes to apply1982 echo "{\"systemMessage\": \"Tests failed after editing $FILE_PATH: $RESULT\"}"
1983fi
1984```
1303 1985
1304This prevents malicious hook modifications from affecting your current session.1986Then add this configuration to `.claude/settings.json` in your project root. The `async: true` flag lets Claude keep working while tests run:
1305 1987
1306## Hook Execution Details1988```json theme={null}
1989{
1990 "hooks": {
1991 "PostToolUse": [
1992 {
1993 "matcher": "Write|Edit",
1994 "hooks": [
1995 {
1996 "type": "command",
1997 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/run-tests-async.sh",
1998 "async": true,
1999 "timeout": 300
2000 }
2001 ]
2002 }
2003 ]
2004 }
2005}
2006```
1307 2007
1308* **Timeout**: 60-second execution limit by default, configurable per command.2008### Limitations
1309 * A timeout for an individual command does not affect the other commands.
1310* **Parallelization**: All matching hooks run in parallel
1311* **Deduplication**: Multiple identical hook commands are deduplicated automatically
1312* **Environment**: Runs in current directory with Claude Code's environment
1313 * The `CLAUDE_PROJECT_DIR` environment variable is available and contains the
1314 absolute path to the project root directory (where Claude Code was started)
1315 * 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.
1316* **Input**: JSON via stdin
1317* **Output**:
1318 * PreToolUse/PermissionRequest/PostToolUse/Stop/SubagentStop: Progress shown in verbose mode (ctrl+o)
1319 * Notification/SessionEnd: Logged to debug only (`--debug`)
1320 * UserPromptSubmit/SessionStart/Setup: stdout added as context for Claude
1321 2009
1322## Debugging2010Async hooks have several constraints compared to synchronous hooks:
1323 2011
1324### Basic Troubleshooting2012* Only `type: "command"` hooks support `async`. Prompt-based hooks cannot run asynchronously.
2013* Async hooks cannot block tool calls or return decisions. By the time the hook completes, the triggering action has already proceeded.
2014* Hook output is delivered on the next conversation turn. If the session is idle, the response waits until the next user interaction.
2015* Each execution creates a separate background process. There is no deduplication across multiple firings of the same async hook.
1325 2016
1326If your hooks aren't working:2017## Security considerations
1327 2018
13281. **Check configuration** - Run `/hooks` to see if your hook is registered2019### Disclaimer
13292. **Verify syntax** - Ensure your JSON settings are valid
13303. **Test commands** - Run hook commands manually first
13314. **Check permissions** - Make sure scripts are executable
13325. **Review logs** - Use `claude --debug` to see hook execution details
1333 2020
1334Common issues:2021Command hooks run with your system user's full permissions.
1335 2022
1336* **Quotes not escaped** - Use `\"` inside JSON strings2023<Warning>
1337* **Wrong matcher** - Check tool names match exactly (case-sensitive)2024 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.
1338* **Command not found** - Use full paths for scripts2025</Warning>
1339 2026
1340### Advanced Debugging2027### Security best practices
1341 2028
1342For complex hook issues:2029Keep these practices in mind when writing hooks:
1343 2030
13441. **Inspect hook execution** - Use `claude --debug` to see detailed hook2031* **Validate and sanitize inputs**: never trust input data blindly
1345 execution2032* **Always quote shell variables**: use `"$VAR"` not `$VAR`
13462. **Validate JSON schemas** - Test hook input/output with external tools2033* **Block path traversal**: check for `..` in file paths
13473. **Check environment variables** - Verify Claude Code's environment is correct2034* **Use absolute paths**: specify full paths for scripts, using `"$CLAUDE_PROJECT_DIR"` for the project root
13484. **Test edge cases** - Try hooks with unusual file paths or inputs2035* **Skip sensitive files**: avoid `.env`, `.git/`, keys, etc.
13495. **Monitor system resources** - Check for resource exhaustion during hook
1350 execution
13516. **Use structured logging** - Implement logging in your hook scripts
1352 2036
1353### Debug Output Example2037## Debug hooks
1354 2038
1355Use `claude --debug` to see hook execution details:2039Run `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.
1356 2040
1357```2041```text theme={null}
1358[DEBUG] Executing hooks for PostToolUse:Write2042[DEBUG] Executing hooks for PostToolUse:Write
1359[DEBUG] Getting matching hook commands for PostToolUse with query: Write2043[DEBUG] Getting matching hook commands for PostToolUse with query: Write
1360[DEBUG] Found 1 hook matchers in settings2044[DEBUG] Found 1 hook matchers in settings
1361[DEBUG] Matched 1 hooks for query "Write"2045[DEBUG] Matched 1 hooks for query "Write"
1362[DEBUG] Found 1 hook commands to execute2046[DEBUG] Found 1 hook commands to execute
1363[DEBUG] Executing hook command: <Your command> with timeout 60000ms2047[DEBUG] Executing hook command: <Your command> with timeout 600000ms
1364[DEBUG] Hook command completed with status 0: <Your stdout>2048[DEBUG] Hook command completed with status 0: <Your stdout>
1365```2049```
1366 2050
1367Progress messages appear in verbose mode (ctrl+o) showing:2051For 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.
1368
1369* Which hook is running
1370* Command being executed
1371* Success/failure status
1372* Output or error messages
1373
1374
1375
1376> To find navigation and other pages in this documentation, fetch the llms.txt file at: https://code.claude.com/docs/llms.txt