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, prompt hooks, and MCP tool hooks.
4 8
5<Tip>9<Tip>
6 For a quickstart guide with examples, see [Get started with Claude Code hooks](/en/hooks-guide).10 For a quickstart guide with examples, see [Automate workflows with hooks](/en/hooks-guide).
7</Tip>11</Tip>
8 12
9## Configuration13Hooks are user-defined shell commands 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 and MCP tool hooks. If you're setting up hooks for the first time, start with the [guide](/en/hooks-guide) instead.
14
15## Hook lifecycle
16
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, this arrives on stdin. 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:
10 18
11Claude Code hooks are configured in your [settings files](/en/settings):19<div style={{maxWidth: "500px", margin: "0 auto"}}>
20 <Frame>
21 <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" />
22 </Frame>
23</div>
12 24
13* `~/.claude/settings.json` - User settings25The 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.
14* `.claude/settings.json` - Project settings
15* `.claude/settings.local.json` - Local project settings (not committed)
16* Enterprise managed policy settings
17 26
18### Structure27| Event | When it fires |
28| :------------------- | :--------------------------------------------------- |
29| `SessionStart` | When a session begins or resumes |
30| `UserPromptSubmit` | When you submit a prompt, before Claude processes it |
31| `PreToolUse` | Before a tool call executes. Can block it |
32| `PermissionRequest` | When a permission dialog appears |
33| `PostToolUse` | After a tool call succeeds |
34| `PostToolUseFailure` | After a tool call fails |
35| `Notification` | When Claude Code sends a notification |
36| `SubagentStart` | When a subagent is spawned |
37| `SubagentStop` | When a subagent finishes |
38| `Stop` | When Claude finishes responding |
39| `PreCompact` | Before context compaction |
40| `SessionEnd` | When a session terminates |
19 41
20Hooks are organized by matchers, where each matcher can have multiple hooks:42### How a hook resolves
43
44To 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:
21 45
22```json theme={null}46```json theme={null}
23{47{
24 "hooks": {48 "hooks": {
25 "EventName": [49 "PreToolUse": [
26 {50 {
27 "matcher": "ToolPattern",51 "matcher": "Bash",
28 "hooks": [52 "hooks": [
29 {53 {
30 "type": "command",54 "type": "command",
31 "command": "your-command-here"55 "command": ".claude/hooks/block-rm.sh"
32 }56 }
33 ]57 ]
34 }58 }
37}61}
38```62```
39 63
40* **matcher**: Pattern to match tool names, case-sensitive (only applicable for64The script reads the JSON input from stdin, extracts the command, and returns a `permissionDecision` of `"deny"` if it contains `rm -rf`:
41 `PreToolUse`, `PermissionRequest`, and `PostToolUse`)
42 * Simple strings match exactly: `Write` matches only the Write tool
43 * Supports regex: `Edit|Write` or `Notebook.*`
44 * Use `*` to match all tools. You can also use empty string (`""`) or leave
45 `matcher` blank.
46* **hooks**: Array of hooks to execute when the pattern matches
47 * `type`: Hook execution type - `"command"` for bash commands or `"prompt"` for LLM-based evaluation
48 * `command`: (For `type: "command"`) The bash command to execute (can use `$CLAUDE_PROJECT_DIR` environment variable)
49 * `prompt`: (For `type: "prompt"`) The prompt to send to the LLM for evaluation
50 * `timeout`: (Optional) How long a hook should run, in seconds, before canceling that specific hook
51
52For events like `UserPromptSubmit`, `Stop`, and `SubagentStop`
53that don't use matchers, you can omit the matcher field:
54 65
55```json theme={null}66```bash theme={null}
56{67#!/bin/bash
57 "hooks": {68# .claude/hooks/block-rm.sh
58 "UserPromptSubmit": [69COMMAND=$(jq -r '.tool_input.command')
59 {70
60 "hooks": [71if echo "$COMMAND" | grep -q 'rm -rf'; then
61 {72 jq -n '{
62 "type": "command",73 hookSpecificOutput: {
63 "command": "/path/to/prompt-validator.py"74 hookEventName: "PreToolUse",
64 }75 permissionDecision: "deny",
65 ]76 permissionDecisionReason: "Destructive command blocked by hook"
66 }
67 ]
68 }77 }
69}78 }'
79else
80 exit 0 # allow the command
81fi
70```82```
71 83
72### Project-Specific Hook Scripts84Now suppose Claude Code decides to run `Bash "rm -rf /tmp/build"`. Here's what happens:
73 85
74You can use the environment variable `CLAUDE_PROJECT_DIR` (only available when86<Frame>
75Claude Code spawns the hook command) to reference scripts stored in your project,87 <img src="https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=7c13f51ffcbc37d22a593b27e2f2de72" alt="Hook resolution flow: PreToolUse event fires, matcher checks for Bash match, hook handler runs, result returns to Claude Code" data-og-width="780" width="780" data-og-height="290" height="290" data-path="images/hook-resolution.svg" data-optimize="true" data-opv="3" srcset="https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=280&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=36a39a07e8bc1995dcb4639e09846905 280w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=560&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=6568d90c596c7605bbac2c325b0a0c86 560w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=840&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=255a6f68b9475a0e41dbde7b88002dad 840w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=1100&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=dcecf8d5edc88cd2bc49deb006d5760d 1100w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=1650&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=04fe51bf69ae375e9fd517f18674e35f 1650w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=2500&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=b1b76e0b77fddb5c7fa7bf302dacd80b 2500w" />
76ensuring they work regardless of Claude's current directory:88</Frame>
77 89
78```json theme={null}90<Steps>
79{91 <Step title="Event fires">
80 "hooks": {92 The `PreToolUse` event fires. Claude Code sends the tool input as JSON on stdin to the hook:
81 "PostToolUse": [93
82 {94 ```json theme={null}
83 "matcher": "Write|Edit",95 { "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... }
84 "hooks": [96 ```
97 </Step>
98
99 <Step title="Matcher checks">
100 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.
101 </Step>
102
103 <Step title="Hook handler runs">
104 The script extracts `"rm -rf /tmp/build"` from the input and finds `rm -rf`, so it prints a decision to stdout:
105
106 ```json theme={null}
85 {107 {
86 "type": "command",108 "hookSpecificOutput": {
87 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-style.sh"109 "hookEventName": "PreToolUse",
88 }110 "permissionDecision": "deny",
89 ]111 "permissionDecisionReason": "Destructive command blocked by hook"
90 }112 }
91 ]
92 }113 }
93}114 ```
94```115
116 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.
117 </Step>
118
119 <Step title="Claude Code acts on the result">
120 Claude Code reads the JSON decision, blocks the tool call, and shows Claude the reason.
121 </Step>
122</Steps>
123
124The [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.
125
126## Configuration
127
128Hooks are defined in JSON settings files. The configuration has three levels of nesting:
129
1301. Choose a [hook event](#hook-events) to respond to, like `PreToolUse` or `Stop`
1312. Add a [matcher group](#matcher-patterns) to filter when it fires, like "only for the Bash tool"
1323. Define one or more [hook handlers](#hook-handler-fields) to run when matched
95 133
96### Plugin hooks134See [How a hook resolves](#how-a-hook-resolves) above for a complete walkthrough with an annotated example.
97 135
98[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.136<Note>
137 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, prompt, or agent that runs. "Hook" on its own refers to the general feature.
138</Note>
139
140### Hook locations
141
142Where you define a hook determines its scope:
143
144| Location | Scope | Shareable |
145| :--------------------------------------------------------- | :---------------------------- | :--------------------------------- |
146| `~/.claude/settings.json` | All your projects | No, local to your machine |
147| `.claude/settings.json` | Single project | Yes, can be committed to the repo |
148| `.claude/settings.local.json` | Single project | No, gitignored |
149| Managed policy settings | Organization-wide | Yes, admin-controlled |
150| [Plugin](/en/plugins) `hooks/hooks.json` | When plugin is enabled | Yes, bundled with the plugin |
151| [Skill](/en/skills) or [agent](/en/sub-agents) frontmatter | While the component is active | Yes, defined in the component file |
152
153For 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).
154
155### Matcher patterns
156
157The `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:
99 158
100**How plugin hooks work**:159| Event | What the matcher filters | Example matcher values |
160| :--------------------------------------------------------------------- | :------------------------ | :----------------------------------------------------------------------------- |
161| `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest` | tool name | `Bash`, `Edit\|Write`, `mcp__.*` |
162| `SessionStart` | how the session started | `startup`, `resume`, `clear`, `compact` |
163| `SessionEnd` | why the session ended | `clear`, `logout`, `prompt_input_exit`, `bypass_permissions_disabled`, `other` |
164| `Notification` | notification type | `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog` |
165| `SubagentStart` | agent type | `Bash`, `Explore`, `Plan`, or custom agent names |
166| `PreCompact` | what triggered compaction | `manual`, `auto` |
167| `SubagentStop` | agent type | same values as `SubagentStart` |
168| `UserPromptSubmit`, `Stop` | no matcher support | always fires on every occurrence |
101 169
102* 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.170The 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.
103* When a plugin is enabled, its hooks are merged with user and project hooks
104* Multiple hooks from different sources can respond to the same event
105* Plugin hooks use the `${CLAUDE_PLUGIN_ROOT}` environment variable to reference plugin files
106 171
107**Example plugin hook configuration**:172This example runs a linting script only when Claude writes or edits a file:
108 173
109```json theme={null}174```json theme={null}
110{175{
111 "description": "Automatic code formatting",
112 "hooks": {176 "hooks": {
113 "PostToolUse": [177 "PostToolUse": [
114 {178 {
115 "matcher": "Write|Edit",179 "matcher": "Edit|Write",
116 "hooks": [180 "hooks": [
117 {181 {
118 "type": "command",182 "type": "command",
119 "command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",183 "command": "/path/to/lint-check.sh"
120 "timeout": 30
121 }184 }
122 ]185 ]
123 }186 }
126}189}
127```190```
128 191
129<Note>192`UserPromptSubmit` and `Stop` don't support matchers and always fire on every occurrence. If you add a `matcher` field to these events, it is silently ignored.
130 Plugin hooks use the same format as regular hooks with an optional `description` field to explain the hook's purpose.
131</Note>
132
133<Note>
134 Plugin hooks run alongside your custom hooks. If multiple hooks match an event, they all execute in parallel.
135</Note>
136
137**Environment variables for plugins**:
138 193
139* `${CLAUDE_PLUGIN_ROOT}`: Absolute path to the plugin directory194#### Match MCP tools
140* `${CLAUDE_PROJECT_DIR}`: Project root directory (same as for project hooks)
141* All standard environment variables are available
142 195
143See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.196[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.
144 197
145## Prompt-Based Hooks198MCP tools follow the naming pattern `mcp__<server>__<tool>`, for example:
146 199
147In 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.200* `mcp__memory__create_entities`: Memory server's create entities tool
148 201* `mcp__filesystem__read_file`: Filesystem server's read file tool
149### How prompt-based hooks work202* `mcp__github__search_repositories`: GitHub server's search tool
150 203
151Instead of executing a bash command, prompt-based hooks:204Use regex patterns to target specific MCP tools or groups of tools:
152 205
1531. Send the hook input and your prompt to a fast LLM (Haiku)206* `mcp__memory__.*` matches all tools from the `memory` server
1542. The LLM responds with structured JSON containing a decision207* `mcp__.*__write.*` matches any tool containing "write" from any server
1553. Claude Code processes the decision automatically
156 208
157### Configuration209This example logs all memory server operations and validates write operations from any MCP server:
158 210
159```json theme={null}211```json theme={null}
160{212{
161 "hooks": {213 "hooks": {
162 "Stop": [214 "PreToolUse": [
163 {215 {
216 "matcher": "mcp__memory__.*",
164 "hooks": [217 "hooks": [
165 {218 {
166 "type": "prompt",219 "type": "command",
167 "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."220 "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
221 }
222 ]
223 },
224 {
225 "matcher": "mcp__.*__write.*",
226 "hooks": [
227 {
228 "type": "command",
229 "command": "/home/user/scripts/validate-mcp-write.py"
168 }230 }
169 ]231 ]
170 }232 }
173}235}
174```236```
175 237
176**Fields:**238### Hook handler fields
177 239
178* `type`: Must be `"prompt"`240Each object in the inner `hooks` array is a hook handler: the shell command, LLM prompt, or agent that runs when the matcher matches. There are three types:
179* `prompt`: The prompt text to send to the LLM
180 * Use `$ARGUMENTS` as a placeholder for the hook input JSON
181 * If `$ARGUMENTS` is not present, input JSON is appended to the prompt
182* `timeout`: (Optional) Timeout in seconds (default: 30 seconds)
183 241
184### Response schema242* **[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.
243* **[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).
244* **[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).
185 245
186The LLM must respond with JSON containing:246#### Common fields
187 247
188```json theme={null}248These fields apply to all hook types:
189{
190 "decision": "approve" | "block",
191 "reason": "Explanation for the decision",
192 "continue": false, // Optional: stops Claude entirely
193 "stopReason": "Message shown to user", // Optional: custom stop message
194 "systemMessage": "Warning or context" // Optional: shown to user
195}
196```
197 249
198**Response fields:**250| Field | Required | Description |
251| :-------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------- |
252| `type` | yes | `"command"`, `"prompt"`, or `"agent"` |
253| `timeout` | no | Seconds before canceling. Defaults: 600 for command, 30 for prompt, 60 for agent |
254| `statusMessage` | no | Custom spinner message displayed while the hook runs |
255| `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) |
199 256
200* `decision`: `"approve"` allows the action, `"block"` prevents it257#### Command hook fields
201* `reason`: Explanation shown to Claude when decision is `"block"`
202* `continue`: (Optional) If `false`, stops Claude's execution entirely
203* `stopReason`: (Optional) Message shown when `continue` is false
204* `systemMessage`: (Optional) Additional message shown to the user
205 258
206### Supported hook events259In addition to the [common fields](#common-fields), command hooks accept these fields:
207 260
208Prompt-based hooks work with any hook event, but are most useful for:261| Field | Required | Description |
262| :-------- | :------- | :------------------------------------------------------------------------------------------------------------------ |
263| `command` | yes | Shell command to execute |
264| `async` | no | If `true`, runs in the background without blocking. See [Run hooks in the background](#run-hooks-in-the-background) |
209 265
210* **Stop**: Intelligently decide if Claude should continue working266#### Prompt and agent hook fields
211* **SubagentStop**: Evaluate if a subagent has completed its task
212* **UserPromptSubmit**: Validate user prompts with LLM assistance
213* **PreToolUse**: Make context-aware permission decisions
214* **PermissionRequest**: Intelligently allow or deny permission dialogs
215 267
216### Example: Intelligent Stop hook268In addition to the [common fields](#common-fields), prompt and agent hooks accept these fields:
217 269
218```json theme={null}270| Field | Required | Description |
219{271| :------- | :------- | :------------------------------------------------------------------------------------------ |
272| `prompt` | yes | Prompt text to send to the model. Use `$ARGUMENTS` as a placeholder for the hook input JSON |
273| `model` | no | Model to use for evaluation. Defaults to a fast model |
274
275All matching hooks run in parallel, and identical handlers are deduplicated automatically. 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.
276
277### Reference scripts by path
278
279Use environment variables to reference hook scripts relative to the project or plugin root, regardless of the working directory when the hook runs:
280
281* `$CLAUDE_PROJECT_DIR`: the project root. Wrap in quotes to handle paths with spaces.
282* `${CLAUDE_PLUGIN_ROOT}`: the plugin's root directory, for scripts bundled with a [plugin](/en/plugins).
283
284<Tabs>
285 <Tab title="Project scripts">
286 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:
287
288 ```json theme={null}
289 {
220 "hooks": {290 "hooks": {
221 "Stop": [291 "PostToolUse": [
222 {292 {
293 "matcher": "Write|Edit",
223 "hooks": [294 "hooks": [
224 {295 {
225 "type": "prompt",296 "type": "command",
226 "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: {\"decision\": \"approve\" or \"block\", \"reason\": \"your explanation\"}",297 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-style.sh"
227 "timeout": 30
228 }298 }
229 ]299 ]
230 }300 }
231 ]301 ]
232 }302 }
233}303 }
234```304 ```
305 </Tab>
235 306
236### Example: SubagentStop with custom logic307 <Tab title="Plugin scripts">
308 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.
237 309
238```json theme={null}310 This example runs a formatting script bundled with the plugin:
239{311
312 ```json theme={null}
313 {
314 "description": "Automatic code formatting",
240 "hooks": {315 "hooks": {
241 "SubagentStop": [316 "PostToolUse": [
242 {317 {
318 "matcher": "Write|Edit",
243 "hooks": [319 "hooks": [
244 {320 {
245 "type": "prompt",321 "type": "command",
246 "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: {\"decision\": \"approve\" or \"block\", \"reason\": \"explanation\"}"322 "command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",
323 "timeout": 30
247 }324 }
248 ]325 ]
249 }326 }
250 ]327 ]
251 }328 }
252}329 }
330 ```
331
332 See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.
333 </Tab>
334</Tabs>
335
336### Hooks in skills and agents
337
338In 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.
339
340All hook events are supported. For subagents, `Stop` hooks are automatically converted to `SubagentStop` since that is the event that fires when a subagent completes.
341
342Hooks use the same configuration format as settings-based hooks but are scoped to the component's lifetime and cleaned up when it finishes.
343
344This skill defines a `PreToolUse` hook that runs a security validation script before each `Bash` command:
345
346```yaml theme={null}
347---
348name: secure-operations
349description: Perform operations with security checks
350hooks:
351 PreToolUse:
352 - matcher: "Bash"
353 hooks:
354 - type: command
355 command: "./scripts/security-check.sh"
356---
253```357```
254 358
255### Comparison with bash command hooks359Agents use the same format in their YAML frontmatter.
256 360
257| Feature | Bash Command Hooks | Prompt-Based Hooks |361### The `/hooks` menu
258| --------------------- | ----------------------- | ------------------------------ |
259| **Execution** | Runs bash script | Queries LLM |
260| **Decision logic** | You implement in code | LLM evaluates context |
261| **Setup complexity** | Requires script file | Just configure prompt |
262| **Context awareness** | Limited to script logic | Natural language understanding |
263| **Performance** | Fast (local execution) | Slower (API call) |
264| **Use case** | Deterministic rules | Context-aware decisions |
265 362
266### Best practices363Type `/hooks` in Claude Code to open the interactive hooks manager, where you can view, add, and delete hooks without editing settings files directly. For a step-by-step walkthrough, see [Set up your first hook](/en/hooks-guide#set-up-your-first-hook) in the guide.
267 364
268* **Be specific in prompts**: Clearly state what you want the LLM to evaluate365Each hook in the menu is labeled with a bracket prefix indicating its source:
269* **Include decision criteria**: List the factors the LLM should consider
270* **Test your prompts**: Verify the LLM makes correct decisions for your use cases
271* **Set appropriate timeouts**: Default is 30 seconds, adjust if needed
272* **Use for complex decisions**: Bash hooks are better for simple, deterministic rules
273 366
274See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.367* `[User]`: from `~/.claude/settings.json`
368* `[Project]`: from `.claude/settings.json`
369* `[Local]`: from `.claude/settings.local.json`
370* `[Plugin]`: from a plugin's `hooks/hooks.json`, read-only
275 371
276## Hook Events372### Disable or remove hooks
277 373
278### PreToolUse374To remove a hook, delete its entry from the settings JSON file, or use the `/hooks` menu and select the hook to delete it.
279 375
280Runs after Claude creates tool parameters and before processing the tool call.376To temporarily disable all hooks without removing them, set `"disableAllHooks": true` in your settings file or use the toggle in the `/hooks` menu. There is no way to disable an individual hook while keeping it in the configuration.
281 377
282**Common matchers:**378Direct edits to hooks in settings files don't take effect immediately. Claude Code captures a snapshot of hooks at startup and uses it throughout the session. This prevents malicious or accidental hook modifications from taking effect mid-session without your review. If hooks are modified externally, Claude Code warns you and requires review in the `/hooks` menu before changes apply.
283 379
284* `Task` - Subagent tasks (see [subagents documentation](/en/sub-agents))380## Hook input and output
285* `Bash` - Shell commands
286* `Glob` - File pattern matching
287* `Grep` - Content search
288* `Read` - File reading
289* `Edit` - File editing
290* `Write` - File writing
291* `WebFetch`, `WebSearch` - Web operations
292 381
293Use [PreToolUse decision control](#pretooluse-decision-control) to allow, deny, or ask for permission to use the tool.382Hooks receive JSON data via stdin and communicate results through exit codes, stdout, and stderr. 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.
294 383
295### PermissionRequest384### Common input fields
296 385
297Runs when the user is shown a permission dialog.386All hook events receive these fields via stdin as JSON, in addition to event-specific fields documented in each [hook event](#hook-events) section:
298Use [PermissionRequest decision control](#permissionrequest-decision-control) to allow or deny on behalf of the user.
299 387
300Recognizes the same matcher values as PreToolUse.388| Field | Description |
389| :---------------- | :----------------------------------------------------------------------------------------------------------------------------------------- |
390| `session_id` | Current session identifier |
391| `transcript_path` | Path to conversation JSON |
392| `cwd` | Current working directory when the hook is invoked |
393| `permission_mode` | Current [permission mode](/en/permissions#permission-modes): `"default"`, `"plan"`, `"acceptEdits"`, `"dontAsk"`, or `"bypassPermissions"` |
394| `hook_event_name` | Name of the event that fired |
301 395
302### PostToolUse396For example, a `PreToolUse` hook for a Bash command receives this on stdin:
303 397
304Runs immediately after a tool completes successfully.398```json theme={null}
399{
400 "session_id": "abc123",
401 "transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
402 "cwd": "/home/user/my-project",
403 "permission_mode": "default",
404 "hook_event_name": "PreToolUse",
405 "tool_name": "Bash",
406 "tool_input": {
407 "command": "npm test"
408 }
409}
410```
305 411
306Recognizes the same matcher values as PreToolUse.412The `tool_name` and `tool_input` fields are event-specific. Each [hook event](#hook-events) section documents the additional fields for that event.
307 413
308### Notification414### Exit code output
415
416The exit code from your hook command tells Claude Code whether the action should proceed, be blocked, or be ignored.
417
418**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.
419
420**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.
421
422**Any other exit code** is a non-blocking error. stderr is shown in verbose mode (`Ctrl+O`) and execution continues.
423
424For example, a hook command script that blocks dangerous Bash commands:
425
426```bash theme={null}
427#!/bin/bash
428# Reads JSON input from stdin, checks the command
429command=$(jq -r '.tool_input.command' < /dev/stdin)
430
431if [[ "$command" == rm* ]]; then
432 echo "Blocked: rm commands are not allowed" >&2
433 exit 2 # Blocking error: tool call is prevented
434fi
435
436exit 0 # Success: tool call proceeds
437```
309 438
310Runs when Claude Code sends notifications. Supports matchers to filter by notification type.439#### Exit code 2 behavior per event
311 440
312**Common matchers:**441Exit 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.
313 442
314* `permission_prompt` - Permission requests from Claude Code443| Hook event | Can block? | What happens on exit 2 |
315* `idle_prompt` - When Claude is waiting for user input (after 60+ seconds of idle time)444| :------------------- | :--------- | :-------------------------------------------------------- |
316* `auth_success` - Authentication success notifications445| `PreToolUse` | Yes | Blocks the tool call |
317* `elicitation_dialog` - When Claude Code needs input for MCP tool elicitation446| `PermissionRequest` | Yes | Denies the permission |
447| `UserPromptSubmit` | Yes | Blocks prompt processing and erases the prompt |
448| `Stop` | Yes | Prevents Claude from stopping, continues the conversation |
449| `SubagentStop` | Yes | Prevents the subagent from stopping |
450| `PostToolUse` | No | Shows stderr to Claude (tool already ran) |
451| `PostToolUseFailure` | No | Shows stderr to Claude (tool already failed) |
452| `Notification` | No | Shows stderr to user only |
453| `SubagentStart` | No | Shows stderr to user only |
454| `SessionStart` | No | Shows stderr to user only |
455| `SessionEnd` | No | Shows stderr to user only |
456| `PreCompact` | No | Shows stderr to user only |
318 457
319You can use matchers to run different hooks for different notification types, or omit the matcher to run hooks for all notifications.458### JSON output
320 459
321**Example: Different notifications for different types**460Exit 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.
461
462<Note>
463 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.
464</Note>
465
466Your 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.
467
468The JSON object supports three kinds of fields:
469
470* **Universal fields** like `continue` work across all events. These are listed in the table below.
471* **Top-level `decision` and `reason`** are used by some events to block or provide feedback.
472* **`hookSpecificOutput`** is a nested object for events that need richer control. It requires a `hookEventName` field set to the event name.
473
474| Field | Default | Description |
475| :--------------- | :------ | :------------------------------------------------------------------------------------------------------------------------- |
476| `continue` | `true` | If `false`, Claude stops processing entirely after the hook runs. Takes precedence over any event-specific decision fields |
477| `stopReason` | none | Message shown to the user when `continue` is `false`. Not shown to Claude |
478| `suppressOutput` | `false` | If `true`, hides stdout from verbose mode output |
479| `systemMessage` | none | Warning message shown to the user |
480
481To stop Claude entirely regardless of event type:
322 482
323```json theme={null}483```json theme={null}
324{484{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }
325 "hooks": {485```
326 "Notification": [486
327 {487#### Decision control
328 "matcher": "permission_prompt",488
329 "hooks": [489Not 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:
490
491| Events | Decision pattern | Key fields |
492| :-------------------------------------------------------------------- | :------------------- | :---------------------------------------------------------------- |
493| UserPromptSubmit, PostToolUse, PostToolUseFailure, Stop, SubagentStop | Top-level `decision` | `decision: "block"`, `reason` |
494| PreToolUse | `hookSpecificOutput` | `permissionDecision` (allow/deny/ask), `permissionDecisionReason` |
495| PermissionRequest | `hookSpecificOutput` | `decision.behavior` (allow/deny) |
496
497Here are examples of each pattern in action:
498
499<Tabs>
500 <Tab title="Top-level decision">
501 Used by `UserPromptSubmit`, `PostToolUse`, `PostToolUseFailure`, `Stop`, and `SubagentStop`. The only value is `"block"` — to allow the action to proceed, omit `decision` from your JSON, or exit 0 without any JSON at all:
502
503 ```json theme={null}
330 {504 {
331 "type": "command",505 "decision": "block",
332 "command": "/path/to/permission-alert.sh"506 "reason": "Test suite must pass before proceeding"
333 }507 }
334 ]508 ```
335 },509 </Tab>
510
511 <Tab title="PreToolUse">
512 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.
513
514 ```json theme={null}
336 {515 {
337 "matcher": "idle_prompt",516 "hookSpecificOutput": {
338 "hooks": [517 "hookEventName": "PreToolUse",
518 "permissionDecision": "deny",
519 "permissionDecisionReason": "Database writes are not allowed"
520 }
521 }
522 ```
523 </Tab>
524
525 <Tab title="PermissionRequest">
526 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.
527
528 ```json theme={null}
339 {529 {
340 "type": "command",530 "hookSpecificOutput": {
341 "command": "/path/to/idle-notification.sh"531 "hookEventName": "PermissionRequest",
532 "decision": {
533 "behavior": "allow",
534 "updatedInput": {
535 "command": "npm run lint"
342 }536 }
343 ]
344 }537 }
345 ]
346 }538 }
347}539 }
348```540 ```
541 </Tab>
542</Tabs>
349 543
350### UserPromptSubmit544For 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).
351 545
352Runs when the user submits a prompt, before Claude processes it. This allows you546## Hook events
353to add additional context based on the prompt/conversation, validate prompts, or
354block certain types of prompts.
355 547
356### Stop548Each 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.
357 549
358Runs when the main Claude Code agent has finished responding. Does not run if550### SessionStart
359the stoppage occurred due to a user interrupt.
360 551
361### SubagentStop552Runs 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.
362 553
363Runs when a Claude Code subagent (Task tool call) has finished responding.554SessionStart runs on every session, so keep these hooks fast.
364 555
365### PreCompact556The matcher value corresponds to how the session was initiated:
366 557
367Runs before Claude Code is about to run a compact operation.558| Matcher | When it fires |
559| :-------- | :------------------------------------- |
560| `startup` | New session |
561| `resume` | `--resume`, `--continue`, or `/resume` |
562| `clear` | `/clear` |
563| `compact` | Auto or manual compaction |
368 564
369**Matchers:**565#### SessionStart input
370 566
371* `manual` - Invoked from `/compact`567In 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.
372* `auto` - Invoked from auto-compact (due to full context window)
373 568
374### SessionStart569```json theme={null}
570{
571 "session_id": "abc123",
572 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
573 "cwd": "/Users/...",
574 "permission_mode": "default",
575 "hook_event_name": "SessionStart",
576 "source": "startup",
577 "model": "claude-sonnet-4-5-20250929"
578}
579```
375 580
376Runs when Claude Code starts a new session or resumes an existing session (which581#### SessionStart decision control
377currently does start a new session under the hood). Useful for loading in
378development context like existing issues or recent changes to your codebase, installing dependencies, or setting up environment variables.
379 582
380**Matchers:**583Any 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:
381 584
382* `startup` - Invoked from startup585| Field | Description |
383* `resume` - Invoked from `--resume`, `--continue`, or `/resume`586| :------------------ | :------------------------------------------------------------------------ |
384* `clear` - Invoked from `/clear`587| `additionalContext` | String added to Claude's context. Multiple hooks' values are concatenated |
385* `compact` - Invoked from auto or manual compact.588
589```json theme={null}
590{
591 "hookSpecificOutput": {
592 "hookEventName": "SessionStart",
593 "additionalContext": "My additional context here"
594 }
595}
596```
386 597
387#### Persisting environment variables598#### Persist environment variables
388 599
389SessionStart 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.600SessionStart 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.
390 601
391**Example: Setting individual environment variables**602To set individual environment variables, write `export` statements to `CLAUDE_ENV_FILE`. Use append (`>>`) to preserve variables set by other hooks:
392 603
393```bash theme={null}604```bash theme={null}
394#!/bin/bash605#!/bin/bash
395 606
396if [ -n "$CLAUDE_ENV_FILE" ]; then607if [ -n "$CLAUDE_ENV_FILE" ]; then
397 echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"608 echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
398 echo 'export API_KEY=your-api-key' >> "$CLAUDE_ENV_FILE"609 echo 'export DEBUG_LOG=true' >> "$CLAUDE_ENV_FILE"
399 echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"610 echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
400fi611fi
401 612
402exit 0613exit 0
403```614```
404 615
405**Example: Persisting all environment changes from the hook**616To capture all environment changes from setup commands, compare the exported variables before and after:
406
407When your setup modifies the environment (e.g., `nvm use`), capture and persist all changes by diffing the environment:
408 617
409```bash theme={null}618```bash theme={null}
410#!/bin/bash619#!/bin/bash
423exit 0632exit 0
424```633```
425 634
426Any variables written to this file will be available in all subsequent bash commands that Claude Code executes during the session.635Any variables written to this file will be available in all subsequent Bash commands that Claude Code executes during the session.
427 636
428<Note>637<Note>
429 `CLAUDE_ENV_FILE` is only available for SessionStart hooks. Other hook types do not have access to this variable.638 `CLAUDE_ENV_FILE` is available for SessionStart hooks. Other hook types do not have access to this variable.
430</Note>639</Note>
431 640
432### SessionEnd641### UserPromptSubmit
433
434Runs when a Claude Code session ends. Useful for cleanup tasks, logging session
435statistics, or saving session state.
436
437The `reason` field in the hook input will be one of:
438
439* `clear` - Session cleared with /clear command
440* `logout` - User logged out
441* `prompt_input_exit` - User exited while prompt input was visible
442* `other` - Other exit reasons
443
444## Hook Input
445
446Hooks receive JSON data via stdin containing session information and
447event-specific data:
448 642
449```typescript theme={null}643Runs when the user submits a prompt, before Claude processes it. This allows you
450{644to add additional context based on the prompt/conversation, validate prompts, or
451 // Common fields645block certain types of prompts.
452 session_id: string
453 transcript_path: string // Path to conversation JSON
454 cwd: string // The current working directory when the hook is invoked
455 permission_mode: string // Current permission mode: "default", "plan", "acceptEdits", or "bypassPermissions"
456
457 // Event-specific fields
458 hook_event_name: string
459 ...
460}
461```
462 646
463### PreToolUse Input647#### UserPromptSubmit input
464 648
465The exact schema for `tool_input` depends on the tool.649In addition to the [common input fields](#common-input-fields), UserPromptSubmit hooks receive the `prompt` field containing the text the user submitted.
466 650
467```json theme={null}651```json theme={null}
468{652{
470 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",654 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
471 "cwd": "/Users/...",655 "cwd": "/Users/...",
472 "permission_mode": "default",656 "permission_mode": "default",
473 "hook_event_name": "PreToolUse",657 "hook_event_name": "UserPromptSubmit",
474 "tool_name": "Write",658 "prompt": "Write a function to calculate the factorial of a number"
659}
660```
661
662#### UserPromptSubmit decision control
663
664`UserPromptSubmit` hooks can control whether a user prompt is processed and add context. All [JSON output fields](#json-output) are available.
665
666There are two ways to add context to the conversation on exit code 0:
667
668* **Plain text stdout**: any non-JSON text written to stdout is added as context
669* **JSON with `additionalContext`**: use the JSON format below for more control. The `additionalContext` field is added as context
670
671Plain stdout is shown as hook output in the transcript. The `additionalContext` field is added more discretely.
672
673To block a prompt, return a JSON object with `decision` set to `"block"`:
674
675| Field | Description |
676| :------------------ | :----------------------------------------------------------------------------------------------------------------- |
677| `decision` | `"block"` prevents the prompt from being processed and erases it from context. Omit to allow the prompt to proceed |
678| `reason` | Shown to the user when `decision` is `"block"`. Not added to context |
679| `additionalContext` | String added to Claude's context |
680
681```json theme={null}
682{
683 "decision": "block",
684 "reason": "Explanation for decision",
685 "hookSpecificOutput": {
686 "hookEventName": "UserPromptSubmit",
687 "additionalContext": "My additional context here"
688 }
689}
690```
691
692<Note>
693 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
694 block prompts or want more structured control.
695</Note>
696
697### PreToolUse
698
699Runs after Claude creates tool parameters and before processing the tool call. Matches on tool name: `Bash`, `Edit`, `Write`, `Read`, `Glob`, `Grep`, `Task`, `WebFetch`, `WebSearch`, and any [MCP tool names](#match-mcp-tools).
700
701Use [PreToolUse decision control](#pretooluse-decision-control) to allow, deny, or ask for permission to use the tool.
702
703#### PreToolUse input
704
705In 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:
706
707##### Bash
708
709Executes shell commands.
710
711| Field | Type | Example | Description |
712| :------------------ | :------ | :----------------- | :-------------------------------------------- |
713| `command` | string | `"npm test"` | The shell command to execute |
714| `description` | string | `"Run test suite"` | Optional description of what the command does |
715| `timeout` | number | `120000` | Optional timeout in milliseconds |
716| `run_in_background` | boolean | `false` | Whether to run the command in background |
717
718##### Write
719
720Creates or overwrites a file.
721
722| Field | Type | Example | Description |
723| :---------- | :----- | :-------------------- | :--------------------------------- |
724| `file_path` | string | `"/path/to/file.txt"` | Absolute path to the file to write |
725| `content` | string | `"file content"` | Content to write to the file |
726
727##### Edit
728
729Replaces a string in an existing file.
730
731| Field | Type | Example | Description |
732| :------------ | :------ | :-------------------- | :--------------------------------- |
733| `file_path` | string | `"/path/to/file.txt"` | Absolute path to the file to edit |
734| `old_string` | string | `"original text"` | Text to find and replace |
735| `new_string` | string | `"replacement text"` | Replacement text |
736| `replace_all` | boolean | `false` | Whether to replace all occurrences |
737
738##### Read
739
740Reads file contents.
741
742| Field | Type | Example | Description |
743| :---------- | :----- | :-------------------- | :----------------------------------------- |
744| `file_path` | string | `"/path/to/file.txt"` | Absolute path to the file to read |
745| `offset` | number | `10` | Optional line number to start reading from |
746| `limit` | number | `50` | Optional number of lines to read |
747
748##### Glob
749
750Finds files matching a glob pattern.
751
752| Field | Type | Example | Description |
753| :-------- | :----- | :--------------- | :--------------------------------------------------------------------- |
754| `pattern` | string | `"**/*.ts"` | Glob pattern to match files against |
755| `path` | string | `"/path/to/dir"` | Optional directory to search in. Defaults to current working directory |
756
757##### Grep
758
759Searches file contents with regular expressions.
760
761| Field | Type | Example | Description |
762| :------------ | :------ | :--------------- | :------------------------------------------------------------------------------------ |
763| `pattern` | string | `"TODO.*fix"` | Regular expression pattern to search for |
764| `path` | string | `"/path/to/dir"` | Optional file or directory to search in |
765| `glob` | string | `"*.ts"` | Optional glob pattern to filter files |
766| `output_mode` | string | `"content"` | `"content"`, `"files_with_matches"`, or `"count"`. Defaults to `"files_with_matches"` |
767| `-i` | boolean | `true` | Case insensitive search |
768| `multiline` | boolean | `false` | Enable multiline matching |
769
770##### WebFetch
771
772Fetches and processes web content.
773
774| Field | Type | Example | Description |
775| :------- | :----- | :---------------------------- | :----------------------------------- |
776| `url` | string | `"https://example.com/api"` | URL to fetch content from |
777| `prompt` | string | `"Extract the API endpoints"` | Prompt to run on the fetched content |
778
779##### WebSearch
780
781Searches the web.
782
783| Field | Type | Example | Description |
784| :---------------- | :----- | :----------------------------- | :------------------------------------------------ |
785| `query` | string | `"react hooks best practices"` | Search query |
786| `allowed_domains` | array | `["docs.example.com"]` | Optional: only include results from these domains |
787| `blocked_domains` | array | `["spam.example.com"]` | Optional: exclude results from these domains |
788
789##### Task
790
791Spawns a [subagent](/en/sub-agents).
792
793| Field | Type | Example | Description |
794| :-------------- | :----- | :------------------------- | :------------------------------------------- |
795| `prompt` | string | `"Find all API endpoints"` | The task for the agent to perform |
796| `description` | string | `"Find API endpoints"` | Short description of the task |
797| `subagent_type` | string | `"Explore"` | Type of specialized agent to use |
798| `model` | string | `"sonnet"` | Optional model alias to override the default |
799
800#### PreToolUse decision control
801
802`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.
803
804| Field | Description |
805| :------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |
806| `permissionDecision` | `"allow"` bypasses the permission system, `"deny"` prevents the tool call, `"ask"` prompts the user to confirm |
807| `permissionDecisionReason` | For `"allow"` and `"ask"`, shown to the user but not Claude. For `"deny"`, shown to Claude |
808| `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 |
809| `additionalContext` | String added to Claude's context before the tool executes |
810
811```json theme={null}
812{
813 "hookSpecificOutput": {
814 "hookEventName": "PreToolUse",
815 "permissionDecision": "allow",
816 "permissionDecisionReason": "My reason here",
817 "updatedInput": {
818 "field_to_modify": "new value"
819 },
820 "additionalContext": "Current environment: production. Proceed with caution."
821 }
822}
823```
824
825<Note>
826 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.
827</Note>
828
829### PermissionRequest
830
831Runs when the user is shown a permission dialog.
832Use [PermissionRequest decision control](#permissionrequest-decision-control) to allow or deny on behalf of the user.
833
834Matches on tool name, same values as PreToolUse.
835
836#### PermissionRequest input
837
838PermissionRequest 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.
839
840```json theme={null}
841{
842 "session_id": "abc123",
843 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
844 "cwd": "/Users/...",
845 "permission_mode": "default",
846 "hook_event_name": "PermissionRequest",
847 "tool_name": "Bash",
475 "tool_input": {848 "tool_input": {
476 "file_path": "/path/to/file.txt",849 "command": "rm -rf node_modules",
477 "content": "file content"850 "description": "Remove node_modules directory"
478 },851 },
479 "tool_use_id": "toolu_01ABC123..."852 "permission_suggestions": [
853 { "type": "toolAlwaysAllow", "tool": "Bash" }
854 ]
480}855}
481```856```
482 857
483### PostToolUse Input858#### PermissionRequest decision control
484 859
485The exact schema for `tool_input` and `tool_response` depends on the tool.860`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:
861
862| Field | Description |
863| :------------------- | :------------------------------------------------------------------------------------------------------------- |
864| `behavior` | `"allow"` grants the permission, `"deny"` denies it |
865| `updatedInput` | For `"allow"` only: modifies the tool's input parameters before execution |
866| `updatedPermissions` | For `"allow"` only: applies permission rule updates, equivalent to the user selecting an "always allow" option |
867| `message` | For `"deny"` only: tells Claude why the permission was denied |
868| `interrupt` | For `"deny"` only: if `true`, stops Claude |
869
870```json theme={null}
871{
872 "hookSpecificOutput": {
873 "hookEventName": "PermissionRequest",
874 "decision": {
875 "behavior": "allow",
876 "updatedInput": {
877 "command": "npm run lint"
878 }
879 }
880 }
881}
882```
883
884### PostToolUse
885
886Runs immediately after a tool completes successfully.
887
888Matches on tool name, same values as PreToolUse.
889
890#### PostToolUse input
891
892`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.
486 893
487```json theme={null}894```json theme={null}
488{895{
504}911}
505```912```
506 913
507### Notification Input914#### PostToolUse decision control
915
916`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:
917
918| Field | Description |
919| :--------------------- | :----------------------------------------------------------------------------------------- |
920| `decision` | `"block"` prompts Claude with the `reason`. Omit to allow the action to proceed |
921| `reason` | Explanation shown to Claude when `decision` is `"block"` |
922| `additionalContext` | Additional context for Claude to consider |
923| `updatedMCPToolOutput` | For [MCP tools](#match-mcp-tools) only: replaces the tool's output with the provided value |
508 924
509```json theme={null}925```json theme={null}
510{926{
511 "session_id": "abc123",927 "decision": "block",
512 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",928 "reason": "Explanation for decision",
513 "cwd": "/Users/...",929 "hookSpecificOutput": {
514 "permission_mode": "default",930 "hookEventName": "PostToolUse",
515 "hook_event_name": "Notification",931 "additionalContext": "Additional information for Claude"
516 "message": "Claude needs your permission to use Bash",932 }
517 "notification_type": "permission_prompt"
518}933}
519```934```
520 935
521### UserPromptSubmit Input936### PostToolUseFailure
937
938Runs 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.
939
940Matches on tool name, same values as PreToolUse.
941
942#### PostToolUseFailure input
943
944PostToolUseFailure hooks receive the same `tool_name` and `tool_input` fields as PostToolUse, along with error information as top-level fields:
522 945
523```json theme={null}946```json theme={null}
524{947{
526 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",949 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
527 "cwd": "/Users/...",950 "cwd": "/Users/...",
528 "permission_mode": "default",951 "permission_mode": "default",
529 "hook_event_name": "UserPromptSubmit",952 "hook_event_name": "PostToolUseFailure",
530 "prompt": "Write a function to calculate the factorial of a number"953 "tool_name": "Bash",
954 "tool_input": {
955 "command": "npm test",
956 "description": "Run test suite"
957 },
958 "tool_use_id": "toolu_01ABC123...",
959 "error": "Command exited with non-zero status code 1",
960 "is_interrupt": false
531}961}
532```962```
533 963
534### Stop and SubagentStop Input964| Field | Description |
965| :------------- | :------------------------------------------------------------------------------ |
966| `error` | String describing what went wrong |
967| `is_interrupt` | Optional boolean indicating whether the failure was caused by user interruption |
535 968
536`stop_hook_active` is true when Claude Code is already continuing as a result of969#### PostToolUseFailure decision control
537a stop hook. Check this value or process the transcript to prevent Claude Code970
538from running indefinitely.971`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:
972
973| Field | Description |
974| :------------------ | :------------------------------------------------------------ |
975| `additionalContext` | Additional context for Claude to consider alongside the error |
539 976
540```json theme={null}977```json theme={null}
541{978{
542 "session_id": "abc123",979 "hookSpecificOutput": {
543 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",980 "hookEventName": "PostToolUseFailure",
544 "permission_mode": "default",981 "additionalContext": "Additional information about the failure for Claude"
545 "hook_event_name": "Stop",982 }
546 "stop_hook_active": true
547}983}
548```984```
549 985
550### PreCompact Input986### Notification
987
988Runs 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.
551 989
552For `manual`, `custom_instructions` comes from what the user passes into990Use 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:
553`/compact`. For `auto`, `custom_instructions` is empty.
554 991
555```json theme={null}992```json theme={null}
556{993{
557 "session_id": "abc123",994 "hooks": {
558 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",995 "Notification": [
559 "permission_mode": "default",996 {
560 "hook_event_name": "PreCompact",997 "matcher": "permission_prompt",
561 "trigger": "manual",998 "hooks": [
562 "custom_instructions": ""999 {
1000 "type": "command",
1001 "command": "/path/to/permission-alert.sh"
1002 }
1003 ]
1004 },
1005 {
1006 "matcher": "idle_prompt",
1007 "hooks": [
1008 {
1009 "type": "command",
1010 "command": "/path/to/idle-notification.sh"
1011 }
1012 ]
1013 }
1014 ]
1015 }
563}1016}
564```1017```
565 1018
566### SessionStart Input1019#### Notification input
1020
1021In 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.
567 1022
568```json theme={null}1023```json theme={null}
569{1024{
570 "session_id": "abc123",1025 "session_id": "abc123",
571 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1026 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1027 "cwd": "/Users/...",
572 "permission_mode": "default",1028 "permission_mode": "default",
573 "hook_event_name": "SessionStart",1029 "hook_event_name": "Notification",
574 "source": "startup"1030 "message": "Claude needs your permission to use Bash",
1031 "title": "Permission needed",
1032 "notification_type": "permission_prompt"
575}1033}
576```1034```
577 1035
578### SessionEnd Input1036Notification 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:
1037
1038| Field | Description |
1039| :------------------ | :------------------------------- |
1040| `additionalContext` | String added to Claude's context |
1041
1042### SubagentStart
1043
1044Runs when a Claude Code subagent is spawned via the Task tool. Supports matchers to filter by agent type name (built-in agents like `Bash`, `Explore`, `Plan`, or custom agent names from `.claude/agents/`).
1045
1046#### SubagentStart input
1047
1048In 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).
579 1049
580```json theme={null}1050```json theme={null}
581{1051{
582 "session_id": "abc123",1052 "session_id": "abc123",
583 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1053 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
584 "cwd": "/Users/...",1054 "cwd": "/Users/...",
585 "permission_mode": "default",1055 "permission_mode": "default",
586 "hook_event_name": "SessionEnd",1056 "hook_event_name": "SubagentStart",
587 "reason": "exit"1057 "agent_id": "agent-abc123",
1058 "agent_type": "Explore"
588}1059}
589```1060```
590 1061
591## Hook Output1062SubagentStart 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:
592 1063
593There are two mutually-exclusive ways for hooks to return output back to Claude Code. The output1064| Field | Description |
594communicates whether to block and any feedback that should be shown to Claude1065| :------------------ | :------------------------------------- |
595and the user.1066| `additionalContext` | String added to the subagent's context |
596 1067
597### Simple: Exit Code1068```json theme={null}
1069{
1070 "hookSpecificOutput": {
1071 "hookEventName": "SubagentStart",
1072 "additionalContext": "Follow security guidelines for this task"
1073 }
1074}
1075```
598 1076
599Hooks communicate status through exit codes, stdout, and stderr:1077### SubagentStop
600 1078
601* **Exit code 0**: Success. `stdout` is shown to the user in verbose mode1079Runs when a Claude Code subagent has finished responding. Matches on agent type, same values as SubagentStart.
602 (ctrl+o), except for `UserPromptSubmit` and `SessionStart`, where stdout is
603 added to the context. JSON output in `stdout` is parsed for structured control
604 (see [Advanced: JSON Output](#advanced-json-output)).
605* **Exit code 2**: Blocking error. Only `stderr` is used as the error message
606 and fed back to Claude. The format is `[command]: {stderr}`. JSON in `stdout`
607 is **not** processed for exit code 2. See per-hook-event behavior below.
608* **Other exit codes**: Non-blocking error. `stderr` is shown to the user in verbose mode (ctrl+o) with
609 format `Failed with non-blocking status code: {stderr}`. If `stderr` is empty,
610 it shows `No stderr output`. Execution continues.
611 1080
612<Warning>1081#### SubagentStop input
613 Reminder: Claude Code does not see stdout if the exit code is 0, except for
614 the `UserPromptSubmit` hook where stdout is injected as context.
615</Warning>
616 1082
617#### Exit Code 2 Behavior1083In addition to the [common input fields](#common-input-fields), SubagentStop hooks receive `stop_hook_active`, `agent_id`, `agent_type`, and `agent_transcript_path`. 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.
618 1084
619| Hook Event | Behavior |1085```json theme={null}
620| ------------------- | ------------------------------------------------------------------ |1086{
621| `PreToolUse` | Blocks the tool call, shows stderr to Claude |1087 "session_id": "abc123",
622| `PermissionRequest` | Denies the permission, shows stderr to Claude |1088 "transcript_path": "~/.claude/projects/.../abc123.jsonl",
623| `PostToolUse` | Shows stderr to Claude (tool already ran) |1089 "cwd": "/Users/...",
624| `Notification` | N/A, shows stderr to user only |1090 "permission_mode": "default",
625| `UserPromptSubmit` | Blocks prompt processing, erases prompt, shows stderr to user only |1091 "hook_event_name": "SubagentStop",
626| `Stop` | Blocks stoppage, shows stderr to Claude |1092 "stop_hook_active": false,
627| `SubagentStop` | Blocks stoppage, shows stderr to Claude subagent |1093 "agent_id": "def456",
628| `PreCompact` | N/A, shows stderr to user only |1094 "agent_type": "Explore",
629| `SessionStart` | N/A, shows stderr to user only |1095 "agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl"
630| `SessionEnd` | N/A, shows stderr to user only |1096}
1097```
631 1098
632### Advanced: JSON Output1099SubagentStop hooks use the same decision control format as [Stop hooks](#stop-decision-control).
633 1100
634Hooks can return structured JSON in `stdout` for more sophisticated control.1101### Stop
635 1102
636<Warning>1103Runs when the main Claude Code agent has finished responding. Does not run if
637 JSON output is only processed when the hook exits with code 0. If your hook1104the stoppage occurred due to a user interrupt.
638 exits with code 2 (blocking error), `stderr` text is used directly—any JSON in `stdout`
639 is ignored. For other non-zero exit codes, only `stderr` is shown to the user in verbose mode (ctrl+o).
640</Warning>
641 1105
642#### Common JSON Fields1106#### Stop input
643 1107
644All hook types can include these optional fields:1108In addition to the [common input fields](#common-input-fields), Stop hooks receive `stop_hook_active`. This 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.
645 1109
646```json theme={null}1110```json theme={null}
647{1111{
648 "continue": true, // Whether Claude should continue after hook execution (default: true)1112 "session_id": "abc123",
649 "stopReason": "string", // Message shown when continue is false1113 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
650 1114 "cwd": "/Users/...",
651 "suppressOutput": true, // Hide stdout from transcript mode (default: false)1115 "permission_mode": "default",
652 "systemMessage": "string" // Optional warning message shown to the user1116 "hook_event_name": "Stop",
1117 "stop_hook_active": true
653}1118}
654```1119```
655 1120
656If `continue` is false, Claude stops processing after the hooks run.1121#### Stop decision control
657
658* For `PreToolUse`, this is different from `"permissionDecision": "deny"`, which
659 only blocks a specific tool call and provides automatic feedback to Claude.
660* For `PostToolUse`, this is different from `"decision": "block"`, which
661 provides automated feedback to Claude.
662* For `UserPromptSubmit`, this prevents the prompt from being processed.
663* For `Stop` and `SubagentStop`, this takes precedence over any
664 `"decision": "block"` output.
665* In all cases, `"continue" = false` takes precedence over any
666 `"decision": "block"` output.
667
668`stopReason` accompanies `continue` with a reason shown to the user, not shown
669to Claude.
670
671#### `PreToolUse` Decision Control
672
673`PreToolUse` hooks can control whether a tool call proceeds.
674 1122
675* `"allow"` bypasses the permission system. `permissionDecisionReason` is shown1123`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:
676 to the user but not to Claude.
677* `"deny"` prevents the tool call from executing. `permissionDecisionReason` is
678 shown to Claude.
679* `"ask"` asks the user to confirm the tool call in the UI.
680 `permissionDecisionReason` is shown to the user but not to Claude.
681 1124
682Additionally, hooks can modify tool inputs before execution using `updatedInput`:1125| Field | Description |
683 1126| :--------- | :------------------------------------------------------------------------- |
684* `updatedInput` allows you to modify the tool's input parameters before the tool executes.1127| `decision` | `"block"` prevents Claude from stopping. Omit to allow Claude to stop |
685* This is most useful with `"permissionDecision": "allow"` to modify and approve tool calls.1128| `reason` | Required when `decision` is `"block"`. Tells Claude why it should continue |
686 1129
687```json theme={null}1130```json theme={null}
688{1131{
689 "hookSpecificOutput": {1132 "decision": "block",
690 "hookEventName": "PreToolUse",1133 "reason": "Must be provided when Claude is blocked from stopping"
691 "permissionDecision": "allow"
692 "permissionDecisionReason": "My reason here",
693 "updatedInput": {
694 "field_to_modify": "new value"
695 }
696 }
697}1134}
698```1135```
699 1136
700<Note>1137### PreCompact
701 The `decision` and `reason` fields are deprecated for PreToolUse hooks.1138
702 Use `hookSpecificOutput.permissionDecision` and1139Runs before Claude Code is about to run a compact operation.
703 `hookSpecificOutput.permissionDecisionReason` instead. The deprecated fields
704 `"approve"` and `"block"` map to `"allow"` and `"deny"` respectively.
705</Note>
706 1140
707#### `PermissionRequest` Decision Control1141The matcher value indicates whether compaction was triggered manually or automatically:
708 1142
709`PermissionRequest` hooks can allow or deny permission requests shown to the user.1143| Matcher | When it fires |
1144| :------- | :------------------------------------------- |
1145| `manual` | `/compact` |
1146| `auto` | Auto-compact when the context window is full |
710 1147
711* For `"behavior": "allow"` you can also optionally pass in an `"updatedInput"` that modifies the tool's input parameters before the tool executes.1148#### PreCompact input
712* 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.1149
1150In 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.
713 1151
714```json theme={null}1152```json theme={null}
715{1153{
716 "hookSpecificOutput": {1154 "session_id": "abc123",
717 "hookEventName": "PermissionRequest",1155 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
718 "decision": {1156 "cwd": "/Users/...",
719 "behavior": "allow",1157 "permission_mode": "default",
720 "updatedInput": {1158 "hook_event_name": "PreCompact",
721 "command": "npm run lint"1159 "trigger": "manual",
722 }1160 "custom_instructions": ""
723 }
724 }
725}1161}
726```1162```
727 1163
728#### `PostToolUse` Decision Control1164### SessionEnd
1165
1166Runs when a Claude Code session ends. Useful for cleanup tasks, logging session
1167statistics, or saving session state. Supports matchers to filter by exit reason.
1168
1169The `reason` field in the hook input indicates why the session ended:
729 1170
730`PostToolUse` hooks can provide feedback to Claude after tool execution.1171| Reason | Description |
1172| :---------------------------- | :----------------------------------------- |
1173| `clear` | Session cleared with `/clear` command |
1174| `logout` | User logged out |
1175| `prompt_input_exit` | User exited while prompt input was visible |
1176| `bypass_permissions_disabled` | Bypass permissions mode was disabled |
1177| `other` | Other exit reasons |
731 1178
732* `"block"` automatically prompts Claude with `reason`.1179#### SessionEnd input
733* `undefined` does nothing. `reason` is ignored.1180
734* `"hookSpecificOutput.additionalContext"` adds context for Claude to consider.1181In 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.
735 1182
736```json theme={null}1183```json theme={null}
737{1184{
738 "decision": "block" | undefined,1185 "session_id": "abc123",
739 "reason": "Explanation for decision",1186 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
740 "hookSpecificOutput": {1187 "cwd": "/Users/...",
741 "hookEventName": "PostToolUse",1188 "permission_mode": "default",
742 "additionalContext": "Additional information for Claude"1189 "hook_event_name": "SessionEnd",
743 }1190 "reason": "other"
744}1191}
745```1192```
746 1193
747#### `UserPromptSubmit` Decision Control1194SessionEnd hooks have no decision control. They cannot block session termination but can perform cleanup tasks.
1195
1196## Prompt-based hooks
748 1197
749`UserPromptSubmit` hooks can control whether a user prompt is processed and add context.1198In 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 work with the following events: `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `UserPromptSubmit`, `Stop`, and `SubagentStop`.
750 1199
751**Adding context (exit code 0):**1200### How prompt-based hooks work
752There are two ways to add context to the conversation:
753 1201
7541. **Plain text stdout** (simpler): Any non-JSON text written to stdout is added1202Instead of executing a Bash command, prompt-based hooks:
755 as context. This is the easiest way to inject information.
756 1203
7572. **JSON with `additionalContext`** (structured): Use the JSON format below for12041. Send the hook input and your prompt to a Claude model, Haiku by default
758 more control. The `additionalContext` field is added as context.12052. The LLM responds with structured JSON containing a decision
12063. Claude Code processes the decision automatically
759 1207
760Both methods work with exit code 0. Plain stdout is shown as hook output in1208### Prompt hook configuration
761the transcript; `additionalContext` is added more discretely.
762 1209
763**Blocking prompts:**1210Set `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.
764 1211
765* `"decision": "block"` prevents the prompt from being processed. The submitted1212This `Stop` hook asks the LLM to evaluate whether all tasks are complete before allowing Claude to finish:
766 prompt is erased from context. `"reason"` is shown to the user but not added
767 to context.
768* `"decision": undefined` (or omitted) allows the prompt to proceed normally.
769 1213
770```json theme={null}1214```json theme={null}
771{1215{
772 "decision": "block" | undefined,1216 "hooks": {
773 "reason": "Explanation for decision",1217 "Stop": [
774 "hookSpecificOutput": {1218 {
775 "hookEventName": "UserPromptSubmit",1219 "hooks": [
776 "additionalContext": "My additional context here"1220 {
1221 "type": "prompt",
1222 "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."
1223 }
1224 ]
1225 }
1226 ]
777 }1227 }
778}1228}
779```1229```
780 1230
781<Note>1231| Field | Required | Description |
782 The JSON format is not required for simple use cases. To add context, you can1232| :-------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
783 just print plain text to stdout with exit code 0. Use JSON when you need to1233| `type` | yes | Must be `"prompt"` |
784 block prompts or want more structured control.1234| `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 |
785</Note>1235| `model` | no | Model to use for evaluation. Defaults to a fast model |
1236| `timeout` | no | Timeout in seconds. Default: 30 |
786 1237
787#### `Stop`/`SubagentStop` Decision Control1238### Response schema
788
789`Stop` and `SubagentStop` hooks can control whether Claude must continue.
790 1239
791* `"block"` prevents Claude from stopping. You must populate `reason` for Claude1240The LLM must respond with JSON containing:
792 to know how to proceed.
793* `undefined` allows Claude to stop. `reason` is ignored.
794 1241
795```json theme={null}1242```json theme={null}
796{1243{
797 "decision": "block" | undefined,1244 "ok": true | false,
798 "reason": "Must be provided when Claude is blocked from stopping"1245 "reason": "Explanation for the decision"
799}1246}
800```1247```
801 1248
802#### `SessionStart` Decision Control1249| Field | Description |
1250| :------- | :--------------------------------------------------------- |
1251| `ok` | `true` allows the action, `false` prevents it |
1252| `reason` | Required when `ok` is `false`. Explanation shown to Claude |
803 1253
804`SessionStart` hooks allow you to load in context at the start of a session.1254### Example: Multi-criteria Stop hook
805 1255
806* `"hookSpecificOutput.additionalContext"` adds the string to the context.1256This `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:
807* Multiple hooks' `additionalContext` values are concatenated.
808 1257
809```json theme={null}1258```json theme={null}
810{1259{
811 "hookSpecificOutput": {1260 "hooks": {
812 "hookEventName": "SessionStart",1261 "Stop": [
813 "additionalContext": "My additional context here"1262 {
1263 "hooks": [
1264 {
1265 "type": "prompt",
1266 "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.",
1267 "timeout": 30
1268 }
1269 ]
1270 }
1271 ]
814 }1272 }
815}1273}
816```1274```
817 1275
818#### `SessionEnd` Decision Control1276## Agent-based hooks
819
820`SessionEnd` hooks run when a session ends. They cannot block session termination
821but can perform cleanup tasks.
822
823#### Exit Code Example: Bash Command Validation
824
825```python theme={null}
826#!/usr/bin/env python3
827import json
828import re
829import sys
830
831# Define validation rules as a list of (regex pattern, message) tuples
832VALIDATION_RULES = [
833 (
834 r"\bgrep\b(?!.*\|)",
835 "Use 'rg' (ripgrep) instead of 'grep' for better performance and features",
836 ),
837 (
838 r"\bfind\s+\S+\s+-name\b",
839 "Use 'rg --files | rg pattern' or 'rg --files -g pattern' instead of 'find -name' for better performance",
840 ),
841]
842
843
844def validate_command(command: str) -> list[str]:
845 issues = []
846 for pattern, message in VALIDATION_RULES:
847 if re.search(pattern, command):
848 issues.append(message)
849 return issues
850
851
852try:
853 input_data = json.load(sys.stdin)
854except json.JSONDecodeError as e:
855 print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
856 sys.exit(1)
857
858tool_name = input_data.get("tool_name", "")
859tool_input = input_data.get("tool_input", {})
860command = tool_input.get("command", "")
861
862if tool_name != "Bash" or not command:
863 sys.exit(1)
864
865# Validate the command
866issues = validate_command(command)
867
868if issues:
869 for message in issues:
870 print(f"• {message}", file=sys.stderr)
871 # Exit code 2 blocks tool call and shows stderr to Claude
872 sys.exit(2)
873```
874
875#### JSON Output Example: UserPromptSubmit to Add Context and Validation
876 1277
877<Note>1278Agent-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.
878 For `UserPromptSubmit` hooks, you can inject context using either method:
879 1279
880 * **Plain text stdout** with exit code 0: Simplest approach—just print text1280### How agent hooks work
881 * **JSON output** with exit code 0: Use `"decision": "block"` to reject prompts,
882 or `additionalContext` for structured context injection
883 1281
884 Remember: Exit code 2 only uses `stderr` for the error message. To block using1282When an agent hook fires:
885 JSON (with a custom reason), use `"decision": "block"` with exit code 0.
886</Note>
887 1283
888```python theme={null}12841. Claude Code spawns a subagent with your prompt and the hook's JSON input
889#!/usr/bin/env python312852. The subagent can use tools like Read, Grep, and Glob to investigate
890import json12863. After up to 50 turns, the subagent returns a structured `{ "ok": true/false }` decision
891import sys12874. Claude Code processes the decision the same way as a prompt hook
892import re
893import datetime
894
895# Load input from stdin
896try:
897 input_data = json.load(sys.stdin)
898except json.JSONDecodeError as e:
899 print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
900 sys.exit(1)
901
902prompt = input_data.get("prompt", "")
903
904# Check for sensitive patterns
905sensitive_patterns = [
906 (r"(?i)\b(password|secret|key|token)\s*[:=]", "Prompt contains potential secrets"),
907]
908
909for pattern, message in sensitive_patterns:
910 if re.search(pattern, prompt):
911 # Use JSON output to block with a specific reason
912 output = {
913 "decision": "block",
914 "reason": f"Security policy violation: {message}. Please rephrase your request without sensitive information."
915 }
916 print(json.dumps(output))
917 sys.exit(0)
918 1288
919# Add current time to context1289Agent hooks are useful when verification requires inspecting actual files or test output, not just evaluating the hook input data alone.
920context = f"Current time: {datetime.datetime.now()}"
921print(context)
922 1290
923"""1291### Agent hook configuration
924The following is also equivalent:
925print(json.dumps({
926 "hookSpecificOutput": {
927 "hookEventName": "UserPromptSubmit",
928 "additionalContext": context,
929 },
930}))
931"""
932 1292
933# Allow the prompt to proceed with the additional context1293Set `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:
934sys.exit(0)
935```
936 1294
937#### JSON Output Example: PreToolUse with Approval1295| Field | Required | Description |
938 1296| :-------- | :------- | :------------------------------------------------------------------------------------------ |
939```python theme={null}1297| `type` | yes | Must be `"agent"` |
940#!/usr/bin/env python31298| `prompt` | yes | Prompt describing what to verify. Use `$ARGUMENTS` as a placeholder for the hook input JSON |
941import json1299| `model` | no | Model to use. Defaults to a fast model |
942import sys1300| `timeout` | no | Timeout in seconds. Default: 60 |
943
944# Load input from stdin
945try:
946 input_data = json.load(sys.stdin)
947except json.JSONDecodeError as e:
948 print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
949 sys.exit(1)
950
951tool_name = input_data.get("tool_name", "")
952tool_input = input_data.get("tool_input", {})
953
954# Example: Auto-approve file reads for documentation files
955if tool_name == "Read":
956 file_path = tool_input.get("file_path", "")
957 if file_path.endswith((".md", ".mdx", ".txt", ".json")):
958 # Use JSON output to auto-approve the tool call
959 output = {
960 "decision": "approve",
961 "reason": "Documentation file auto-approved",
962 "suppressOutput": True # Don't show in verbose mode
963 }
964 print(json.dumps(output))
965 sys.exit(0)
966 1301
967# For other cases, let the normal permission flow proceed1302The response schema is the same as prompt hooks: `{ "ok": true }` to allow or `{ "ok": false, "reason": "..." }` to block.
968sys.exit(0)
969```
970 1303
971## Working with MCP Tools1304This `Stop` hook verifies that all unit tests pass before allowing Claude to finish:
972 1305
973Claude Code hooks work seamlessly with1306```json theme={null}
974[Model Context Protocol (MCP) tools](/en/mcp). When MCP servers1307{
975provide tools, they appear with a special naming pattern that you can match in1308 "hooks": {
976your hooks.1309 "Stop": [
1310 {
1311 "hooks": [
1312 {
1313 "type": "agent",
1314 "prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
1315 "timeout": 120
1316 }
1317 ]
1318 }
1319 ]
1320 }
1321}
1322```
977 1323
978### MCP Tool Naming1324## Run hooks in the background
979 1325
980MCP tools follow the pattern `mcp__<server>__<tool>`, for example:1326By 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.
981 1327
982* `mcp__memory__create_entities` - Memory server's create entities tool1328### Configure an async hook
983* `mcp__filesystem__read_file` - Filesystem server's read file tool
984* `mcp__github__search_repositories` - GitHub server's search tool
985 1329
986### Configuring Hooks for MCP Tools1330Add `"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.
987 1331
988You can target specific MCP tools or entire MCP servers:1332This 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:
989 1333
990```json theme={null}1334```json theme={null}
991{1335{
992 "hooks": {1336 "hooks": {
993 "PreToolUse": [1337 "PostToolUse": [
994 {
995 "matcher": "mcp__memory__.*",
996 "hooks": [
997 {
998 "type": "command",
999 "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
1000 }
1001 ]
1002 },
1003 {1338 {
1004 "matcher": "mcp__.*__write.*",1339 "matcher": "Write",
1005 "hooks": [1340 "hooks": [
1006 {1341 {
1007 "type": "command",1342 "type": "command",
1008 "command": "/home/user/scripts/validate-mcp-write.py"1343 "command": "/path/to/run-tests.sh",
1344 "async": true,
1345 "timeout": 120
1009 }1346 }
1010 ]1347 ]
1011 }1348 }
1014}1351}
1015```1352```
1016 1353
1017## Examples1354The `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.
1018 1355
1019<Tip>1356### How async hooks execute
1020 For practical examples including code formatting, notifications, and file protection, see [More Examples](/en/hooks-guide#more-examples) in the get started guide.
1021</Tip>
1022
1023## Security Considerations
1024 1357
1025### Disclaimer1358When 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.
1026
1027**USE AT YOUR OWN RISK**: Claude Code hooks execute arbitrary shell commands on
1028your system automatically. By using hooks, you acknowledge that:
1029 1359
1030* You are solely responsible for the commands you configure1360After 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.
1031* Hooks can modify, delete, or access any files your user account can access
1032* Malicious or poorly written hooks can cause data loss or system damage
1033* Anthropic provides no warranty and assumes no liability for any damages
1034 resulting from hook usage
1035* You should thoroughly test hooks in a safe environment before production use
1036 1361
1037Always review and understand any hook commands before adding them to your1362### Example: run tests after file changes
1038configuration.
1039 1363
1040### Security Best Practices1364This 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`:
1041 1365
1042Here are some key practices for writing more secure hooks:1366```bash theme={null}
1367#!/bin/bash
1368# run-tests-async.sh
1043 1369
10441. **Validate and sanitize inputs** - Never trust input data blindly1370# Read hook input from stdin
10452. **Always quote shell variables** - Use `"$VAR"` not `$VAR`1371INPUT=$(cat)
10463. **Block path traversal** - Check for `..` in file paths1372FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
10474. **Use absolute paths** - Specify full paths for scripts (use
1048 "\$CLAUDE\_PROJECT\_DIR" for the project path)
10495. **Skip sensitive files** - Avoid `.env`, `.git/`, keys, etc.
1050 1373
1051### Configuration Safety1374# Only run tests for source files
1375if [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.js ]]; then
1376 exit 0
1377fi
1052 1378
1053Direct edits to hooks in settings files don't take effect immediately. Claude1379# Run tests and report results via systemMessage
1054Code:1380RESULT=$(npm test 2>&1)
1381EXIT_CODE=$?
1055 1382
10561. Captures a snapshot of hooks at startup1383if [ $EXIT_CODE -eq 0 ]; then
10572. Uses this snapshot throughout the session1384 echo "{\"systemMessage\": \"Tests passed after editing $FILE_PATH\"}"
10583. Warns if hooks are modified externally1385else
10594. Requires review in `/hooks` menu for changes to apply1386 echo "{\"systemMessage\": \"Tests failed after editing $FILE_PATH: $RESULT\"}"
1387fi
1388```
1060 1389
1061This prevents malicious hook modifications from affecting your current session.1390Then add this configuration to `.claude/settings.json` in your project root. The `async: true` flag lets Claude keep working while tests run:
1062 1391
1063## Hook Execution Details1392```json theme={null}
1393{
1394 "hooks": {
1395 "PostToolUse": [
1396 {
1397 "matcher": "Write|Edit",
1398 "hooks": [
1399 {
1400 "type": "command",
1401 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/run-tests-async.sh",
1402 "async": true,
1403 "timeout": 300
1404 }
1405 ]
1406 }
1407 ]
1408 }
1409}
1410```
1064 1411
1065* **Timeout**: 60-second execution limit by default, configurable per command.1412### Limitations
1066 * A timeout for an individual command does not affect the other commands.
1067* **Parallelization**: All matching hooks run in parallel
1068* **Deduplication**: Multiple identical hook commands are deduplicated automatically
1069* **Environment**: Runs in current directory with Claude Code's environment
1070 * The `CLAUDE_PROJECT_DIR` environment variable is available and contains the
1071 absolute path to the project root directory (where Claude Code was started)
1072 * 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.
1073* **Input**: JSON via stdin
1074* **Output**:
1075 * PreToolUse/PermissionRequest/PostToolUse/Stop/SubagentStop: Progress shown in verbose mode (ctrl+o)
1076 * Notification/SessionEnd: Logged to debug only (`--debug`)
1077 * UserPromptSubmit/SessionStart: stdout added as context for Claude
1078 1413
1079## Debugging1414Async hooks have several constraints compared to synchronous hooks:
1080 1415
1081### Basic Troubleshooting1416* Only `type: "command"` hooks support `async`. Prompt-based hooks cannot run asynchronously.
1417* Async hooks cannot block tool calls or return decisions. By the time the hook completes, the triggering action has already proceeded.
1418* Hook output is delivered on the next conversation turn. If the session is idle, the response waits until the next user interaction.
1419* Each execution creates a separate background process. There is no deduplication across multiple firings of the same async hook.
1082 1420
1083If your hooks aren't working:1421## Security considerations
1084 1422
10851. **Check configuration** - Run `/hooks` to see if your hook is registered1423### Disclaimer
10862. **Verify syntax** - Ensure your JSON settings are valid
10873. **Test commands** - Run hook commands manually first
10884. **Check permissions** - Make sure scripts are executable
10895. **Review logs** - Use `claude --debug` to see hook execution details
1090 1424
1091Common issues:1425Hooks run with your system user's full permissions.
1092 1426
1093* **Quotes not escaped** - Use `\"` inside JSON strings1427<Warning>
1094* **Wrong matcher** - Check tool names match exactly (case-sensitive)1428 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.
1095* **Command not found** - Use full paths for scripts1429</Warning>
1096 1430
1097### Advanced Debugging1431### Security best practices
1098 1432
1099For complex hook issues:1433Keep these practices in mind when writing hooks:
1100 1434
11011. **Inspect hook execution** - Use `claude --debug` to see detailed hook1435* **Validate and sanitize inputs**: never trust input data blindly
1102 execution1436* **Always quote shell variables**: use `"$VAR"` not `$VAR`
11032. **Validate JSON schemas** - Test hook input/output with external tools1437* **Block path traversal**: check for `..` in file paths
11043. **Check environment variables** - Verify Claude Code's environment is correct1438* **Use absolute paths**: specify full paths for scripts, using `"$CLAUDE_PROJECT_DIR"` for the project root
11054. **Test edge cases** - Try hooks with unusual file paths or inputs1439* **Skip sensitive files**: avoid `.env`, `.git/`, keys, etc.
11065. **Monitor system resources** - Check for resource exhaustion during hook
1107 execution
11086. **Use structured logging** - Implement logging in your hook scripts
1109 1440
1110### Debug Output Example1441## Debug hooks
1111 1442
1112Use `claude --debug` to see hook execution details:1443Run `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.
1113 1444
1114```1445```
1115[DEBUG] Executing hooks for PostToolUse:Write1446[DEBUG] Executing hooks for PostToolUse:Write
1117[DEBUG] Found 1 hook matchers in settings1448[DEBUG] Found 1 hook matchers in settings
1118[DEBUG] Matched 1 hooks for query "Write"1449[DEBUG] Matched 1 hooks for query "Write"
1119[DEBUG] Found 1 hook commands to execute1450[DEBUG] Found 1 hook commands to execute
1120[DEBUG] Executing hook command: <Your command> with timeout 60000ms1451[DEBUG] Executing hook command: <Your command> with timeout 600000ms
1121[DEBUG] Hook command completed with status 0: <Your stdout>1452[DEBUG] Hook command completed with status 0: <Your stdout>
1122```1453```
1123 1454
1124Progress messages appear in verbose mode (ctrl+o) showing:1455For 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.
1125
1126* Which hook is running
1127* Command being executed
1128* Success/failure status
1129* Output or error messages