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.
10 14
11Claude Code hooks are configured in your [settings files](/en/settings):15## Hook lifecycle
12 16
13* `~/.claude/settings.json` - User settings17Hooks fire at specific points during a Claude Code session. When an event fires and a matcher matches, Claude Code passes JSON context about the event to your hook handler. For command hooks, 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:
14* `.claude/settings.json` - Project settings
15* `.claude/settings.local.json` - Local project settings (not committed)
16* Enterprise managed policy settings
17 18
18<Note>19<div style={{maxWidth: "500px", margin: "0 auto"}}>
19 Enterprise administrators can use `allowManagedHooksOnly` to block user, project, and plugin hooks. See [Hook configuration](/en/settings#hook-configuration).20 <Frame>
20</Note>21 <img src="https://mintcdn.com/claude-code/xcAz1d2i2To-I_QJ/images/hooks-lifecycle.svg?fit=max&auto=format&n=xcAz1d2i2To-I_QJ&q=85&s=783a0db47dd59602418763e037056d49" alt="Hook lifecycle diagram showing the sequence of hooks from SessionStart through the agentic loop to SessionEnd" data-og-width="520" width="520" data-og-height="960" height="960" data-path="images/hooks-lifecycle.svg" data-optimize="true" data-opv="3" srcset="https://mintcdn.com/claude-code/xcAz1d2i2To-I_QJ/images/hooks-lifecycle.svg?w=280&fit=max&auto=format&n=xcAz1d2i2To-I_QJ&q=85&s=1fd947ad1c8fc4fcfbe85c8b4b7b528b 280w, https://mintcdn.com/claude-code/xcAz1d2i2To-I_QJ/images/hooks-lifecycle.svg?w=560&fit=max&auto=format&n=xcAz1d2i2To-I_QJ&q=85&s=794ba776ed6126344835c206f587c9dd 560w, https://mintcdn.com/claude-code/xcAz1d2i2To-I_QJ/images/hooks-lifecycle.svg?w=840&fit=max&auto=format&n=xcAz1d2i2To-I_QJ&q=85&s=d137272c869dd6f9315ec35f99338289 840w, https://mintcdn.com/claude-code/xcAz1d2i2To-I_QJ/images/hooks-lifecycle.svg?w=1100&fit=max&auto=format&n=xcAz1d2i2To-I_QJ&q=85&s=531c5f866a6fd56adf94ecfa156ac96a 1100w, https://mintcdn.com/claude-code/xcAz1d2i2To-I_QJ/images/hooks-lifecycle.svg?w=1650&fit=max&auto=format&n=xcAz1d2i2To-I_QJ&q=85&s=dc81c6d273cd26cd7f9a191ddcb92592 1650w, https://mintcdn.com/claude-code/xcAz1d2i2To-I_QJ/images/hooks-lifecycle.svg?w=2500&fit=max&auto=format&n=xcAz1d2i2To-I_QJ&q=85&s=8f29af9b4145e517655a8bdf7a9987c5 2500w" />
22 </Frame>
23</div>
21 24
22### Structure25The 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.
23 26
24Hooks are organized by matchers, where each matcher can have multiple hooks:27| 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| `TeammateIdle` | When an [agent team](/en/agent-teams) teammate is about to go idle |
40| `TaskCompleted` | When a task is being marked as completed |
41| `ConfigChange` | When a configuration file changes during a session |
42| `PreCompact` | Before context compaction |
43| `SessionEnd` | When a session terminates |
44
45### How a hook resolves
46
47To 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:
25 48
26```json theme={null}49```json theme={null}
27{50{
28 "hooks": {51 "hooks": {
29 "EventName": [52 "PreToolUse": [
30 {53 {
31 "matcher": "ToolPattern",54 "matcher": "Bash",
32 "hooks": [55 "hooks": [
33 {56 {
34 "type": "command",57 "type": "command",
35 "command": "your-command-here"58 "command": ".claude/hooks/block-rm.sh"
36 }59 }
37 ]60 ]
38 }61 }
41}64}
42```65```
43 66
44* **matcher**: Pattern to match tool names, case-sensitive (only applicable for67The script reads the JSON input from stdin, extracts the command, and returns a `permissionDecision` of `"deny"` if it contains `rm -rf`:
45 `PreToolUse`, `PermissionRequest`, and `PostToolUse`)
46 * Simple strings match exactly: `Write` matches only the Write tool
47 * Supports regex: `Edit|Write` or `Notebook.*`
48 * Use `*` to match all tools. You can also use empty string (`""`) or leave
49 `matcher` blank.
50* **hooks**: Array of hooks to execute when the pattern matches
51 * `type`: Hook execution type - `"command"` for bash commands or `"prompt"` for LLM-based evaluation
52 * `command`: (For `type: "command"`) The bash command to execute (can use `$CLAUDE_PROJECT_DIR` environment variable)
53 * `prompt`: (For `type: "prompt"`) The prompt to send to the LLM for evaluation
54 * `timeout`: (Optional) How long a hook should run, in seconds, before canceling that specific hook
55
56For events like `UserPromptSubmit`, `Stop`, and `SubagentStop`
57that don't use matchers, you can omit the matcher field:
58 68
59```json theme={null}69```bash theme={null}
60{70#!/bin/bash
61 "hooks": {71# .claude/hooks/block-rm.sh
62 "UserPromptSubmit": [72COMMAND=$(jq -r '.tool_input.command')
63 {73
64 "hooks": [74if echo "$COMMAND" | grep -q 'rm -rf'; then
65 {75 jq -n '{
66 "type": "command",76 hookSpecificOutput: {
67 "command": "/path/to/prompt-validator.py"77 hookEventName: "PreToolUse",
68 }78 permissionDecision: "deny",
69 ]79 permissionDecisionReason: "Destructive command blocked by hook"
70 }
71 ]
72 }80 }
73}81 }'
82else
83 exit 0 # allow the command
84fi
74```85```
75 86
76### Project-Specific Hook Scripts87Now suppose Claude Code decides to run `Bash "rm -rf /tmp/build"`. Here's what happens:
77 88
78You can use the environment variable `CLAUDE_PROJECT_DIR` (only available when89<Frame>
79Claude Code spawns the hook command) to reference scripts stored in your project,90 <img src="https://mintcdn.com/claude-code/TBPmHzr19mDCuhZi/images/hook-resolution.svg?fit=max&auto=format&n=TBPmHzr19mDCuhZi&q=85&s=5bb890134390ecd0581477cf41ef730b" 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/TBPmHzr19mDCuhZi/images/hook-resolution.svg?w=280&fit=max&auto=format&n=TBPmHzr19mDCuhZi&q=85&s=5dcaecd24c260b8a90365d74e2c1fcda 280w, https://mintcdn.com/claude-code/TBPmHzr19mDCuhZi/images/hook-resolution.svg?w=560&fit=max&auto=format&n=TBPmHzr19mDCuhZi&q=85&s=c03d91c279f01d92e58ddd70fdbe66f2 560w, https://mintcdn.com/claude-code/TBPmHzr19mDCuhZi/images/hook-resolution.svg?w=840&fit=max&auto=format&n=TBPmHzr19mDCuhZi&q=85&s=1be57a4819cbb949a5ea9d08a05c9ecd 840w, https://mintcdn.com/claude-code/TBPmHzr19mDCuhZi/images/hook-resolution.svg?w=1100&fit=max&auto=format&n=TBPmHzr19mDCuhZi&q=85&s=0e9dd1807dc7a5c56011d0889b0d5208 1100w, https://mintcdn.com/claude-code/TBPmHzr19mDCuhZi/images/hook-resolution.svg?w=1650&fit=max&auto=format&n=TBPmHzr19mDCuhZi&q=85&s=69496ac02e70fabfece087ba31a1dcfc 1650w, https://mintcdn.com/claude-code/TBPmHzr19mDCuhZi/images/hook-resolution.svg?w=2500&fit=max&auto=format&n=TBPmHzr19mDCuhZi&q=85&s=a012346cb46a33b86580348802055267 2500w" />
80ensuring they work regardless of Claude's current directory:91</Frame>
81 92
82```json theme={null}93<Steps>
83{94 <Step title="Event fires">
84 "hooks": {95 The `PreToolUse` event fires. Claude Code sends the tool input as JSON on stdin to the hook:
85 "PostToolUse": [96
86 {97 ```json theme={null}
87 "matcher": "Write|Edit",98 { "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... }
88 "hooks": [99 ```
100 </Step>
101
102 <Step title="Matcher checks">
103 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.
104 </Step>
105
106 <Step title="Hook handler runs">
107 The script extracts `"rm -rf /tmp/build"` from the input and finds `rm -rf`, so it prints a decision to stdout:
108
109 ```json theme={null}
89 {110 {
90 "type": "command",111 "hookSpecificOutput": {
91 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-style.sh"112 "hookEventName": "PreToolUse",
113 "permissionDecision": "deny",
114 "permissionDecisionReason": "Destructive command blocked by hook"
92 }115 }
93 ]
94 }116 }
95 ]117 ```
96 }118
97}119 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.
98```120 </Step>
121
122 <Step title="Claude Code acts on the result">
123 Claude Code reads the JSON decision, blocks the tool call, and shows Claude the reason.
124 </Step>
125</Steps>
126
127The [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.
128
129## Configuration
130
131Hooks are defined in JSON settings files. The configuration has three levels of nesting:
132
1331. Choose a [hook event](#hook-events) to respond to, like `PreToolUse` or `Stop`
1342. Add a [matcher group](#matcher-patterns) to filter when it fires, like "only for the Bash tool"
1353. Define one or more [hook handlers](#hook-handler-fields) to run when matched
136
137See [How a hook resolves](#how-a-hook-resolves) above for a complete walkthrough with an annotated example.
138
139<Note>
140 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.
141</Note>
142
143### Hook locations
144
145Where you define a hook determines its scope:
99 146
100### Plugin hooks147| Location | Scope | Shareable |
148| :--------------------------------------------------------- | :---------------------------- | :--------------------------------- |
149| `~/.claude/settings.json` | All your projects | No, local to your machine |
150| `.claude/settings.json` | Single project | Yes, can be committed to the repo |
151| `.claude/settings.local.json` | Single project | No, gitignored |
152| Managed policy settings | Organization-wide | Yes, admin-controlled |
153| [Plugin](/en/plugins) `hooks/hooks.json` | When plugin is enabled | Yes, bundled with the plugin |
154| [Skill](/en/skills) or [agent](/en/sub-agents) frontmatter | While the component is active | Yes, defined in the component file |
101 155
102[Plugins](/en/plugins) can provide hooks that integrate seamlessly with your user and project hooks. Plugin hooks are automatically merged with your configuration when plugins are enabled.156For 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).
103 157
104**How plugin hooks work**:158### Matcher patterns
105 159
106* Plugin hooks are defined in the plugin's `hooks/hooks.json` file or in a file given by a custom path to the `hooks` field.160The `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:
107* When a plugin is enabled, its hooks are merged with user and project hooks
108* Multiple hooks from different sources can respond to the same event
109* Plugin hooks use the `${CLAUDE_PLUGIN_ROOT}` environment variable to reference plugin files
110 161
111**Example plugin hook configuration**:162| Event | What the matcher filters | Example matcher values |
163| :--------------------------------------------------------------------- | :------------------------ | :--------------------------------------------------------------------------------- |
164| `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest` | tool name | `Bash`, `Edit\|Write`, `mcp__.*` |
165| `SessionStart` | how the session started | `startup`, `resume`, `clear`, `compact` |
166| `SessionEnd` | why the session ended | `clear`, `logout`, `prompt_input_exit`, `bypass_permissions_disabled`, `other` |
167| `Notification` | notification type | `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog` |
168| `SubagentStart` | agent type | `Bash`, `Explore`, `Plan`, or custom agent names |
169| `PreCompact` | what triggered compaction | `manual`, `auto` |
170| `SubagentStop` | agent type | same values as `SubagentStart` |
171| `ConfigChange` | configuration source | `user_settings`, `project_settings`, `local_settings`, `policy_settings`, `skills` |
172| `UserPromptSubmit`, `Stop`, `TeammateIdle`, `TaskCompleted` | no matcher support | always fires on every occurrence |
173
174The 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.
175
176This example runs a linting script only when Claude writes or edits a file:
112 177
113```json theme={null}178```json theme={null}
114{179{
115 "description": "Automatic code formatting",
116 "hooks": {180 "hooks": {
117 "PostToolUse": [181 "PostToolUse": [
118 {182 {
119 "matcher": "Write|Edit",183 "matcher": "Edit|Write",
120 "hooks": [184 "hooks": [
121 {185 {
122 "type": "command",186 "type": "command",
123 "command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",187 "command": "/path/to/lint-check.sh"
124 "timeout": 30
125 }188 }
126 ]189 ]
127 }190 }
130}193}
131```194```
132 195
133<Note>196`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.
134 Plugin hooks use the same format as regular hooks with an optional `description` field to explain the hook's purpose.
135</Note>
136
137<Note>
138 Plugin hooks run alongside your custom hooks. If multiple hooks match an event, they all execute in parallel.
139</Note>
140 197
141**Environment variables for plugins**:198#### Match MCP tools
142 199
143* `${CLAUDE_PLUGIN_ROOT}`: Absolute path to the plugin directory200[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* `${CLAUDE_PROJECT_DIR}`: Project root directory (same as for project hooks)
145* All standard environment variables are available
146 201
147See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.202MCP tools follow the naming pattern `mcp__<server>__<tool>`, for example:
148 203
149## Prompt-Based Hooks204* `mcp__memory__create_entities`: Memory server's create entities tool
205* `mcp__filesystem__read_file`: Filesystem server's read file tool
206* `mcp__github__search_repositories`: GitHub server's search tool
150 207
151In 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.208Use regex patterns to target specific MCP tools or groups of tools:
152 209
153### How prompt-based hooks work210* `mcp__memory__.*` matches all tools from the `memory` server
211* `mcp__.*__write.*` matches any tool containing "write" from any server
154 212
155Instead of executing a bash command, prompt-based hooks:213This example logs all memory server operations and validates write operations from any MCP server:
156
1571. Send the hook input and your prompt to a fast LLM (Haiku)
1582. The LLM responds with structured JSON containing a decision
1593. Claude Code processes the decision automatically
160
161### Configuration
162 214
163```json theme={null}215```json theme={null}
164{216{
165 "hooks": {217 "hooks": {
166 "Stop": [218 "PreToolUse": [
167 {219 {
220 "matcher": "mcp__memory__.*",
168 "hooks": [221 "hooks": [
169 {222 {
170 "type": "prompt",223 "type": "command",
171 "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."224 "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
225 }
226 ]
227 },
228 {
229 "matcher": "mcp__.*__write.*",
230 "hooks": [
231 {
232 "type": "command",
233 "command": "/home/user/scripts/validate-mcp-write.py"
172 }234 }
173 ]235 ]
174 }236 }
177}239}
178```240```
179 241
180**Fields:**242### Hook handler fields
181 243
182* `type`: Must be `"prompt"`244Each 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:
183* `prompt`: The prompt text to send to the LLM
184 * Use `$ARGUMENTS` as a placeholder for the hook input JSON
185 * If `$ARGUMENTS` is not present, input JSON is appended to the prompt
186* `timeout`: (Optional) Timeout in seconds (default: 30 seconds)
187 245
188### Response schema246* **[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.
247* **[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).
248* **[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).
189 249
190The LLM must respond with JSON containing:250#### Common fields
191 251
192```json theme={null}252These fields apply to all hook types:
193{
194 "decision": "approve" | "block",
195 "reason": "Explanation for the decision",
196 "continue": false, // Optional: stops Claude entirely
197 "stopReason": "Message shown to user", // Optional: custom stop message
198 "systemMessage": "Warning or context" // Optional: shown to user
199}
200```
201 253
202**Response fields:**254| Field | Required | Description |
255| :-------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------- |
256| `type` | yes | `"command"`, `"prompt"`, or `"agent"` |
257| `timeout` | no | Seconds before canceling. Defaults: 600 for command, 30 for prompt, 60 for agent |
258| `statusMessage` | no | Custom spinner message displayed while the hook runs |
259| `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) |
203 260
204* `decision`: `"approve"` allows the action, `"block"` prevents it261#### Command hook fields
205* `reason`: Explanation shown to Claude when decision is `"block"`
206* `continue`: (Optional) If `false`, stops Claude's execution entirely
207* `stopReason`: (Optional) Message shown when `continue` is false
208* `systemMessage`: (Optional) Additional message shown to the user
209 262
210### Supported hook events263In addition to the [common fields](#common-fields), command hooks accept these fields:
211 264
212Prompt-based hooks work with any hook event, but are most useful for:265| Field | Required | Description |
266| :-------- | :------- | :------------------------------------------------------------------------------------------------------------------ |
267| `command` | yes | Shell command to execute |
268| `async` | no | If `true`, runs in the background without blocking. See [Run hooks in the background](#run-hooks-in-the-background) |
213 269
214* **Stop**: Intelligently decide if Claude should continue working270#### Prompt and agent hook fields
215* **SubagentStop**: Evaluate if a subagent has completed its task
216* **UserPromptSubmit**: Validate user prompts with LLM assistance
217* **PreToolUse**: Make context-aware permission decisions
218* **PermissionRequest**: Intelligently allow or deny permission dialogs
219 271
220### Example: Intelligent Stop hook272In addition to the [common fields](#common-fields), prompt and agent hooks accept these fields:
221 273
222```json theme={null}274| Field | Required | Description |
223{275| :------- | :------- | :------------------------------------------------------------------------------------------ |
276| `prompt` | yes | Prompt text to send to the model. Use `$ARGUMENTS` as a placeholder for the hook input JSON |
277| `model` | no | Model to use for evaluation. Defaults to a fast model |
278
279All 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.
280
281### Reference scripts by path
282
283Use environment variables to reference hook scripts relative to the project or plugin root, regardless of the working directory when the hook runs:
284
285* `$CLAUDE_PROJECT_DIR`: the project root. Wrap in quotes to handle paths with spaces.
286* `${CLAUDE_PLUGIN_ROOT}`: the plugin's root directory, for scripts bundled with a [plugin](/en/plugins).
287
288<Tabs>
289 <Tab title="Project scripts">
290 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:
291
292 ```json theme={null}
293 {
224 "hooks": {294 "hooks": {
225 "Stop": [295 "PostToolUse": [
226 {296 {
297 "matcher": "Write|Edit",
227 "hooks": [298 "hooks": [
228 {299 {
229 "type": "prompt",300 "type": "command",
230 "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\"}",301 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-style.sh"
231 "timeout": 30
232 }302 }
233 ]303 ]
234 }304 }
235 ]305 ]
236 }306 }
237}307 }
238```308 ```
309 </Tab>
239 310
240### Example: SubagentStop with custom logic311 <Tab title="Plugin scripts">
312 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.
241 313
242```json theme={null}314 This example runs a formatting script bundled with the plugin:
243{315
316 ```json theme={null}
317 {
318 "description": "Automatic code formatting",
244 "hooks": {319 "hooks": {
245 "SubagentStop": [320 "PostToolUse": [
246 {321 {
322 "matcher": "Write|Edit",
247 "hooks": [323 "hooks": [
248 {324 {
249 "type": "prompt",325 "type": "command",
250 "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\"}"326 "command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",
327 "timeout": 30
251 }328 }
252 ]329 ]
253 }330 }
254 ]331 ]
255 }332 }
256}333 }
257```334 ```
258 335
259### Comparison with bash command hooks336 See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.
337 </Tab>
338</Tabs>
260 339
261| Feature | Bash Command Hooks | Prompt-Based Hooks |340### Hooks in skills and agents
262| --------------------- | ----------------------- | ------------------------------ |
263| **Execution** | Runs bash script | Queries LLM |
264| **Decision logic** | You implement in code | LLM evaluates context |
265| **Setup complexity** | Requires script file | Configure prompt |
266| **Context awareness** | Limited to script logic | Natural language understanding |
267| **Performance** | Fast (local execution) | Slower (API call) |
268| **Use case** | Deterministic rules | Context-aware decisions |
269 341
270### Best practices342In 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.
271 343
272* **Be specific in prompts**: Clearly state what you want the LLM to evaluate344All hook events are supported. For subagents, `Stop` hooks are automatically converted to `SubagentStop` since that is the event that fires when a subagent completes.
273* **Include decision criteria**: List the factors the LLM should consider
274* **Test your prompts**: Verify the LLM makes correct decisions for your use cases
275* **Set appropriate timeouts**: Default is 30 seconds, adjust if needed
276* **Use for complex decisions**: Bash hooks are better for simple, deterministic rules
277 345
278See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.346Hooks use the same configuration format as settings-based hooks but are scoped to the component's lifetime and cleaned up when it finishes.
279 347
280## Hook Events348This skill defines a `PreToolUse` hook that runs a security validation script before each `Bash` command:
281 349
282### PreToolUse350```yaml theme={null}
351---
352name: secure-operations
353description: Perform operations with security checks
354hooks:
355 PreToolUse:
356 - matcher: "Bash"
357 hooks:
358 - type: command
359 command: "./scripts/security-check.sh"
360---
361```
283 362
284Runs after Claude creates tool parameters and before processing the tool call.363Agents use the same format in their YAML frontmatter.
285 364
286**Common matchers:**365### The `/hooks` menu
287 366
288* `Task` - Subagent tasks (see [subagents documentation](/en/sub-agents))367Type `/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.
289* `Bash` - Shell commands
290* `Glob` - File pattern matching
291* `Grep` - Content search
292* `Read` - File reading
293* `Edit` - File editing
294* `Write` - File writing
295* `WebFetch`, `WebSearch` - Web operations
296 368
297Use [PreToolUse decision control](#pretooluse-decision-control) to allow, deny, or ask for permission to use the tool.369Each hook in the menu is labeled with a bracket prefix indicating its source:
298 370
299### PermissionRequest371* `[User]`: from `~/.claude/settings.json`
372* `[Project]`: from `.claude/settings.json`
373* `[Local]`: from `.claude/settings.local.json`
374* `[Plugin]`: from a plugin's `hooks/hooks.json`, read-only
300 375
301Runs when the user is shown a permission dialog.376### Disable or remove hooks
302Use [PermissionRequest decision control](#permissionrequest-decision-control) to allow or deny on behalf of the user.
303 377
304Recognizes the same matcher values as PreToolUse.378To remove a hook, delete its entry from the settings JSON file, or use the `/hooks` menu and select the hook to delete it.
305 379
306### PostToolUse380To 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.
307 381
308Runs immediately after a tool completes successfully.382The `disableAllHooks` setting respects the managed settings hierarchy. If an administrator has configured hooks through managed policy settings, `disableAllHooks` set in user, project, or local settings cannot disable those managed hooks. Only `disableAllHooks` set at the managed settings level can disable managed hooks.
309 383
310Recognizes the same matcher values as PreToolUse.384Direct 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.
311 385
312### Notification386## Hook input and output
313 387
314Runs when Claude Code sends notifications. Supports matchers to filter by notification type.388Hooks 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.
315 389
316**Common matchers:**390### Common input fields
317 391
318* `permission_prompt` - Permission requests from Claude Code392All hook events receive these fields via stdin as JSON, in addition to event-specific fields documented in each [hook event](#hook-events) section:
319* `idle_prompt` - When Claude is waiting for user input (after 60+ seconds of idle time)
320* `auth_success` - Authentication success notifications
321* `elicitation_dialog` - When Claude Code needs input for MCP tool elicitation
322 393
323You can use matchers to run different hooks for different notification types, or omit the matcher to run hooks for all notifications.394| Field | Description |
395| :---------------- | :----------------------------------------------------------------------------------------------------------------------------------------- |
396| `session_id` | Current session identifier |
397| `transcript_path` | Path to conversation JSON |
398| `cwd` | Current working directory when the hook is invoked |
399| `permission_mode` | Current [permission mode](/en/permissions#permission-modes): `"default"`, `"plan"`, `"acceptEdits"`, `"dontAsk"`, or `"bypassPermissions"` |
400| `hook_event_name` | Name of the event that fired |
324 401
325**Example: Different notifications for different types**402For example, a `PreToolUse` hook for a Bash command receives this on stdin:
326 403
327```json theme={null}404```json theme={null}
328{405{
329 "hooks": {406 "session_id": "abc123",
330 "Notification": [407 "transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
331 {408 "cwd": "/home/user/my-project",
332 "matcher": "permission_prompt",409 "permission_mode": "default",
333 "hooks": [410 "hook_event_name": "PreToolUse",
411 "tool_name": "Bash",
412 "tool_input": {
413 "command": "npm test"
414 }
415}
416```
417
418The `tool_name` and `tool_input` fields are event-specific. Each [hook event](#hook-events) section documents the additional fields for that event.
419
420### Exit code output
421
422The exit code from your hook command tells Claude Code whether the action should proceed, be blocked, or be ignored.
423
424**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.
425
426**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.
427
428**Any other exit code** is a non-blocking error. stderr is shown in verbose mode (`Ctrl+O`) and execution continues.
429
430For example, a hook command script that blocks dangerous Bash commands:
431
432```bash theme={null}
433#!/bin/bash
434# Reads JSON input from stdin, checks the command
435command=$(jq -r '.tool_input.command' < /dev/stdin)
436
437if [[ "$command" == rm* ]]; then
438 echo "Blocked: rm commands are not allowed" >&2
439 exit 2 # Blocking error: tool call is prevented
440fi
441
442exit 0 # Success: tool call proceeds
443```
444
445#### Exit code 2 behavior per event
446
447Exit 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.
448
449| Hook event | Can block? | What happens on exit 2 |
450| :------------------- | :--------- | :---------------------------------------------------------------------------- |
451| `PreToolUse` | Yes | Blocks the tool call |
452| `PermissionRequest` | Yes | Denies the permission |
453| `UserPromptSubmit` | Yes | Blocks prompt processing and erases the prompt |
454| `Stop` | Yes | Prevents Claude from stopping, continues the conversation |
455| `SubagentStop` | Yes | Prevents the subagent from stopping |
456| `TeammateIdle` | Yes | Prevents the teammate from going idle (teammate continues working) |
457| `TaskCompleted` | Yes | Prevents the task from being marked as completed |
458| `ConfigChange` | Yes | Blocks the configuration change from taking effect (except `policy_settings`) |
459| `PostToolUse` | No | Shows stderr to Claude (tool already ran) |
460| `PostToolUseFailure` | No | Shows stderr to Claude (tool already failed) |
461| `Notification` | No | Shows stderr to user only |
462| `SubagentStart` | No | Shows stderr to user only |
463| `SessionStart` | No | Shows stderr to user only |
464| `SessionEnd` | No | Shows stderr to user only |
465| `PreCompact` | No | Shows stderr to user only |
466
467### JSON output
468
469Exit 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.
470
471<Note>
472 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.
473</Note>
474
475Your 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.
476
477The JSON object supports three kinds of fields:
478
479* **Universal fields** like `continue` work across all events. These are listed in the table below.
480* **Top-level `decision` and `reason`** are used by some events to block or provide feedback.
481* **`hookSpecificOutput`** is a nested object for events that need richer control. It requires a `hookEventName` field set to the event name.
482
483| Field | Default | Description |
484| :--------------- | :------ | :------------------------------------------------------------------------------------------------------------------------- |
485| `continue` | `true` | If `false`, Claude stops processing entirely after the hook runs. Takes precedence over any event-specific decision fields |
486| `stopReason` | none | Message shown to the user when `continue` is `false`. Not shown to Claude |
487| `suppressOutput` | `false` | If `true`, hides stdout from verbose mode output |
488| `systemMessage` | none | Warning message shown to the user |
489
490To stop Claude entirely regardless of event type:
491
492```json theme={null}
493{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }
494```
495
496#### Decision control
497
498Not 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:
499
500| Events | Decision pattern | Key fields |
501| :---------------------------------------------------------------------------------- | :------------------- | :---------------------------------------------------------------- |
502| UserPromptSubmit, PostToolUse, PostToolUseFailure, Stop, SubagentStop, ConfigChange | Top-level `decision` | `decision: "block"`, `reason` |
503| TeammateIdle, TaskCompleted | Exit code only | Exit code 2 blocks the action, stderr is fed back as feedback |
504| PreToolUse | `hookSpecificOutput` | `permissionDecision` (allow/deny/ask), `permissionDecisionReason` |
505| PermissionRequest | `hookSpecificOutput` | `decision.behavior` (allow/deny) |
506
507Here are examples of each pattern in action:
508
509<Tabs>
510 <Tab title="Top-level decision">
511 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:
512
513 ```json theme={null}
334 {514 {
335 "type": "command",515 "decision": "block",
336 "command": "/path/to/permission-alert.sh"516 "reason": "Test suite must pass before proceeding"
337 }517 }
338 ]518 ```
339 },519 </Tab>
520
521 <Tab title="PreToolUse">
522 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.
523
524 ```json theme={null}
340 {525 {
341 "matcher": "idle_prompt",526 "hookSpecificOutput": {
342 "hooks": [527 "hookEventName": "PreToolUse",
528 "permissionDecision": "deny",
529 "permissionDecisionReason": "Database writes are not allowed"
530 }
531 }
532 ```
533 </Tab>
534
535 <Tab title="PermissionRequest">
536 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.
537
538 ```json theme={null}
343 {539 {
344 "type": "command",540 "hookSpecificOutput": {
345 "command": "/path/to/idle-notification.sh"541 "hookEventName": "PermissionRequest",
542 "decision": {
543 "behavior": "allow",
544 "updatedInput": {
545 "command": "npm run lint"
346 }546 }
347 ]
348 }547 }
349 ]
350 }548 }
351}549 }
352```550 ```
551 </Tab>
552</Tabs>
353 553
354### UserPromptSubmit554For 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).
355 555
356Runs when the user submits a prompt, before Claude processes it. This allows you556## Hook events
357to add additional context based on the prompt/conversation, validate prompts, or
358block certain types of prompts.
359 557
360### Stop558Each 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.
361 559
362Runs when the main Claude Code agent has finished responding. Does not run if560### SessionStart
363the stoppage occurred due to a user interrupt.
364 561
365### SubagentStop562Runs 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.
366 563
367Runs when a Claude Code subagent (Task tool call) has finished responding.564SessionStart runs on every session, so keep these hooks fast.
368 565
369### PreCompact566The matcher value corresponds to how the session was initiated:
370 567
371Runs before Claude Code is about to run a compact operation.568| Matcher | When it fires |
569| :-------- | :------------------------------------- |
570| `startup` | New session |
571| `resume` | `--resume`, `--continue`, or `/resume` |
572| `clear` | `/clear` |
573| `compact` | Auto or manual compaction |
372 574
373**Matchers:**575#### SessionStart input
374 576
375* `manual` - Invoked from `/compact`577In 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.
376* `auto` - Invoked from auto-compact (due to full context window)
377 578
378### SessionStart579```json theme={null}
580{
581 "session_id": "abc123",
582 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
583 "cwd": "/Users/...",
584 "permission_mode": "default",
585 "hook_event_name": "SessionStart",
586 "source": "startup",
587 "model": "claude-sonnet-4-6"
588}
589```
379 590
380Runs when Claude Code starts a new session or resumes an existing session (which591#### SessionStart decision control
381currently does start a new session under the hood). Useful for loading in
382development context like existing issues or recent changes to your codebase, installing dependencies, or setting up environment variables.
383 592
384**Matchers:**593Any 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:
385 594
386* `startup` - Invoked from startup595| Field | Description |
387* `resume` - Invoked from `--resume`, `--continue`, or `/resume`596| :------------------ | :------------------------------------------------------------------------ |
388* `clear` - Invoked from `/clear`597| `additionalContext` | String added to Claude's context. Multiple hooks' values are concatenated |
389* `compact` - Invoked from auto or manual compact.
390 598
391#### Persisting environment variables599```json theme={null}
600{
601 "hookSpecificOutput": {
602 "hookEventName": "SessionStart",
603 "additionalContext": "My additional context here"
604 }
605}
606```
607
608#### Persist environment variables
392 609
393SessionStart 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.610SessionStart 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.
394 611
395**Example: Setting individual environment variables**612To set individual environment variables, write `export` statements to `CLAUDE_ENV_FILE`. Use append (`>>`) to preserve variables set by other hooks:
396 613
397```bash theme={null}614```bash theme={null}
398#!/bin/bash615#!/bin/bash
399 616
400if [ -n "$CLAUDE_ENV_FILE" ]; then617if [ -n "$CLAUDE_ENV_FILE" ]; then
401 echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"618 echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
402 echo 'export API_KEY=your-api-key' >> "$CLAUDE_ENV_FILE"619 echo 'export DEBUG_LOG=true' >> "$CLAUDE_ENV_FILE"
403 echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"620 echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
404fi621fi
405 622
406exit 0623exit 0
407```624```
408 625
409**Example: Persisting all environment changes from the hook**626To capture all environment changes from setup commands, compare the exported variables before and after:
410
411When your setup modifies the environment (for example, `nvm use`), capture and persist all changes by diffing the environment:
412 627
413```bash theme={null}628```bash theme={null}
414#!/bin/bash629#!/bin/bash
427exit 0642exit 0
428```643```
429 644
430Any variables written to this file will be available in all subsequent bash commands that Claude Code executes during the session.645Any variables written to this file will be available in all subsequent Bash commands that Claude Code executes during the session.
431 646
432<Note>647<Note>
433 `CLAUDE_ENV_FILE` is only available for SessionStart hooks. Other hook types do not have access to this variable.648 `CLAUDE_ENV_FILE` is available for SessionStart hooks. Other hook types do not have access to this variable.
434</Note>649</Note>
435 650
436### SessionEnd651### UserPromptSubmit
437 652
438Runs when a Claude Code session ends. Useful for cleanup tasks, logging session653Runs when the user submits a prompt, before Claude processes it. This allows you
439statistics, or saving session state.654to add additional context based on the prompt/conversation, validate prompts, or
655block certain types of prompts.
656
657#### UserPromptSubmit input
658
659In addition to the [common input fields](#common-input-fields), UserPromptSubmit hooks receive the `prompt` field containing the text the user submitted.
660
661```json theme={null}
662{
663 "session_id": "abc123",
664 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
665 "cwd": "/Users/...",
666 "permission_mode": "default",
667 "hook_event_name": "UserPromptSubmit",
668 "prompt": "Write a function to calculate the factorial of a number"
669}
670```
671
672#### UserPromptSubmit decision control
673
674`UserPromptSubmit` hooks can control whether a user prompt is processed and add context. All [JSON output fields](#json-output) are available.
675
676There are two ways to add context to the conversation on exit code 0:
677
678* **Plain text stdout**: any non-JSON text written to stdout is added as context
679* **JSON with `additionalContext`**: use the JSON format below for more control. The `additionalContext` field is added as context
680
681Plain stdout is shown as hook output in the transcript. The `additionalContext` field is added more discretely.
682
683To block a prompt, return a JSON object with `decision` set to `"block"`:
684
685| Field | Description |
686| :------------------ | :----------------------------------------------------------------------------------------------------------------- |
687| `decision` | `"block"` prevents the prompt from being processed and erases it from context. Omit to allow the prompt to proceed |
688| `reason` | Shown to the user when `decision` is `"block"`. Not added to context |
689| `additionalContext` | String added to Claude's context |
690
691```json theme={null}
692{
693 "decision": "block",
694 "reason": "Explanation for decision",
695 "hookSpecificOutput": {
696 "hookEventName": "UserPromptSubmit",
697 "additionalContext": "My additional context here"
698 }
699}
700```
701
702<Note>
703 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
704 block prompts or want more structured control.
705</Note>
706
707### PreToolUse
708
709Runs 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).
710
711Use [PreToolUse decision control](#pretooluse-decision-control) to allow, deny, or ask for permission to use the tool.
712
713#### PreToolUse input
714
715In 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:
716
717##### Bash
718
719Executes shell commands.
720
721| Field | Type | Example | Description |
722| :------------------ | :------ | :----------------- | :-------------------------------------------- |
723| `command` | string | `"npm test"` | The shell command to execute |
724| `description` | string | `"Run test suite"` | Optional description of what the command does |
725| `timeout` | number | `120000` | Optional timeout in milliseconds |
726| `run_in_background` | boolean | `false` | Whether to run the command in background |
727
728##### Write
729
730Creates or overwrites a file.
440 731
441The `reason` field in the hook input will be one of:732| Field | Type | Example | Description |
733| :---------- | :----- | :-------------------- | :--------------------------------- |
734| `file_path` | string | `"/path/to/file.txt"` | Absolute path to the file to write |
735| `content` | string | `"file content"` | Content to write to the file |
442 736
443* `clear` - Session cleared with /clear command737##### Edit
444* `logout` - User logged out
445* `prompt_input_exit` - User exited while prompt input was visible
446* `other` - Other exit reasons
447 738
448## Hook Input739Replaces a string in an existing file.
449 740
450Hooks receive JSON data via stdin containing session information and741| Field | Type | Example | Description |
451event-specific data:742| :------------ | :------ | :-------------------- | :--------------------------------- |
743| `file_path` | string | `"/path/to/file.txt"` | Absolute path to the file to edit |
744| `old_string` | string | `"original text"` | Text to find and replace |
745| `new_string` | string | `"replacement text"` | Replacement text |
746| `replace_all` | boolean | `false` | Whether to replace all occurrences |
452 747
453```typescript theme={null}748##### Read
749
750Reads file contents.
751
752| Field | Type | Example | Description |
753| :---------- | :----- | :-------------------- | :----------------------------------------- |
754| `file_path` | string | `"/path/to/file.txt"` | Absolute path to the file to read |
755| `offset` | number | `10` | Optional line number to start reading from |
756| `limit` | number | `50` | Optional number of lines to read |
757
758##### Glob
759
760Finds files matching a glob pattern.
761
762| Field | Type | Example | Description |
763| :-------- | :----- | :--------------- | :--------------------------------------------------------------------- |
764| `pattern` | string | `"**/*.ts"` | Glob pattern to match files against |
765| `path` | string | `"/path/to/dir"` | Optional directory to search in. Defaults to current working directory |
766
767##### Grep
768
769Searches file contents with regular expressions.
770
771| Field | Type | Example | Description |
772| :------------ | :------ | :--------------- | :------------------------------------------------------------------------------------ |
773| `pattern` | string | `"TODO.*fix"` | Regular expression pattern to search for |
774| `path` | string | `"/path/to/dir"` | Optional file or directory to search in |
775| `glob` | string | `"*.ts"` | Optional glob pattern to filter files |
776| `output_mode` | string | `"content"` | `"content"`, `"files_with_matches"`, or `"count"`. Defaults to `"files_with_matches"` |
777| `-i` | boolean | `true` | Case insensitive search |
778| `multiline` | boolean | `false` | Enable multiline matching |
779
780##### WebFetch
781
782Fetches and processes web content.
783
784| Field | Type | Example | Description |
785| :------- | :----- | :---------------------------- | :----------------------------------- |
786| `url` | string | `"https://example.com/api"` | URL to fetch content from |
787| `prompt` | string | `"Extract the API endpoints"` | Prompt to run on the fetched content |
788
789##### WebSearch
790
791Searches the web.
792
793| Field | Type | Example | Description |
794| :---------------- | :----- | :----------------------------- | :------------------------------------------------ |
795| `query` | string | `"react hooks best practices"` | Search query |
796| `allowed_domains` | array | `["docs.example.com"]` | Optional: only include results from these domains |
797| `blocked_domains` | array | `["spam.example.com"]` | Optional: exclude results from these domains |
798
799##### Task
800
801Spawns a [subagent](/en/sub-agents).
802
803| Field | Type | Example | Description |
804| :-------------- | :----- | :------------------------- | :------------------------------------------- |
805| `prompt` | string | `"Find all API endpoints"` | The task for the agent to perform |
806| `description` | string | `"Find API endpoints"` | Short description of the task |
807| `subagent_type` | string | `"Explore"` | Type of specialized agent to use |
808| `model` | string | `"sonnet"` | Optional model alias to override the default |
809
810#### PreToolUse decision control
811
812`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.
813
814| Field | Description |
815| :------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |
816| `permissionDecision` | `"allow"` bypasses the permission system, `"deny"` prevents the tool call, `"ask"` prompts the user to confirm |
817| `permissionDecisionReason` | For `"allow"` and `"ask"`, shown to the user but not Claude. For `"deny"`, shown to Claude |
818| `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 |
819| `additionalContext` | String added to Claude's context before the tool executes |
820
821```json theme={null}
454{822{
455 // Common fields823 "hookSpecificOutput": {
456 session_id: string824 "hookEventName": "PreToolUse",
457 transcript_path: string // Path to conversation JSON825 "permissionDecision": "allow",
458 cwd: string // The current working directory when the hook is invoked826 "permissionDecisionReason": "My reason here",
459 permission_mode: string // Current permission mode: "default", "plan", "acceptEdits", or "bypassPermissions"827 "updatedInput": {
460 828 "field_to_modify": "new value"
461 // Event-specific fields829 },
462 hook_event_name: string830 "additionalContext": "Current environment: production. Proceed with caution."
463 ...831 }
464}832}
465```833```
466 834
467### PreToolUse Input835<Note>
836 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.
837</Note>
838
839### PermissionRequest
840
841Runs when the user is shown a permission dialog.
842Use [PermissionRequest decision control](#permissionrequest-decision-control) to allow or deny on behalf of the user.
843
844Matches on tool name, same values as PreToolUse.
845
846#### PermissionRequest input
468 847
469The exact schema for `tool_input` depends on the tool.848PermissionRequest 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.
470 849
471```json theme={null}850```json theme={null}
472{851{
474 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",853 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
475 "cwd": "/Users/...",854 "cwd": "/Users/...",
476 "permission_mode": "default",855 "permission_mode": "default",
477 "hook_event_name": "PreToolUse",856 "hook_event_name": "PermissionRequest",
478 "tool_name": "Write",857 "tool_name": "Bash",
479 "tool_input": {858 "tool_input": {
480 "file_path": "/path/to/file.txt",859 "command": "rm -rf node_modules",
481 "content": "file content"860 "description": "Remove node_modules directory"
482 },861 },
483 "tool_use_id": "toolu_01ABC123..."862 "permission_suggestions": [
863 { "type": "toolAlwaysAllow", "tool": "Bash" }
864 ]
865}
866```
867
868#### PermissionRequest decision control
869
870`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:
871
872| Field | Description |
873| :------------------- | :------------------------------------------------------------------------------------------------------------- |
874| `behavior` | `"allow"` grants the permission, `"deny"` denies it |
875| `updatedInput` | For `"allow"` only: modifies the tool's input parameters before execution |
876| `updatedPermissions` | For `"allow"` only: applies permission rule updates, equivalent to the user selecting an "always allow" option |
877| `message` | For `"deny"` only: tells Claude why the permission was denied |
878| `interrupt` | For `"deny"` only: if `true`, stops Claude |
879
880```json theme={null}
881{
882 "hookSpecificOutput": {
883 "hookEventName": "PermissionRequest",
884 "decision": {
885 "behavior": "allow",
886 "updatedInput": {
887 "command": "npm run lint"
888 }
889 }
890 }
484}891}
485```892```
486 893
487### PostToolUse Input894### PostToolUse
895
896Runs immediately after a tool completes successfully.
897
898Matches on tool name, same values as PreToolUse.
488 899
489The exact schema for `tool_input` and `tool_response` depends on the tool.900#### PostToolUse input
901
902`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.
490 903
491```json theme={null}904```json theme={null}
492{905{
508}921}
509```922```
510 923
511### Notification Input924#### PostToolUse decision control
925
926`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:
927
928| Field | Description |
929| :--------------------- | :----------------------------------------------------------------------------------------- |
930| `decision` | `"block"` prompts Claude with the `reason`. Omit to allow the action to proceed |
931| `reason` | Explanation shown to Claude when `decision` is `"block"` |
932| `additionalContext` | Additional context for Claude to consider |
933| `updatedMCPToolOutput` | For [MCP tools](#match-mcp-tools) only: replaces the tool's output with the provided value |
934
935```json theme={null}
936{
937 "decision": "block",
938 "reason": "Explanation for decision",
939 "hookSpecificOutput": {
940 "hookEventName": "PostToolUse",
941 "additionalContext": "Additional information for Claude"
942 }
943}
944```
945
946### PostToolUseFailure
947
948Runs 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.
949
950Matches on tool name, same values as PreToolUse.
951
952#### PostToolUseFailure input
953
954PostToolUseFailure hooks receive the same `tool_name` and `tool_input` fields as PostToolUse, along with error information as top-level fields:
955
956```json theme={null}
957{
958 "session_id": "abc123",
959 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
960 "cwd": "/Users/...",
961 "permission_mode": "default",
962 "hook_event_name": "PostToolUseFailure",
963 "tool_name": "Bash",
964 "tool_input": {
965 "command": "npm test",
966 "description": "Run test suite"
967 },
968 "tool_use_id": "toolu_01ABC123...",
969 "error": "Command exited with non-zero status code 1",
970 "is_interrupt": false
971}
972```
973
974| Field | Description |
975| :------------- | :------------------------------------------------------------------------------ |
976| `error` | String describing what went wrong |
977| `is_interrupt` | Optional boolean indicating whether the failure was caused by user interruption |
978
979#### PostToolUseFailure decision control
980
981`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:
982
983| Field | Description |
984| :------------------ | :------------------------------------------------------------ |
985| `additionalContext` | Additional context for Claude to consider alongside the error |
986
987```json theme={null}
988{
989 "hookSpecificOutput": {
990 "hookEventName": "PostToolUseFailure",
991 "additionalContext": "Additional information about the failure for Claude"
992 }
993}
994```
995
996### Notification
997
998Runs 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.
999
1000Use 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:
1001
1002```json theme={null}
1003{
1004 "hooks": {
1005 "Notification": [
1006 {
1007 "matcher": "permission_prompt",
1008 "hooks": [
1009 {
1010 "type": "command",
1011 "command": "/path/to/permission-alert.sh"
1012 }
1013 ]
1014 },
1015 {
1016 "matcher": "idle_prompt",
1017 "hooks": [
1018 {
1019 "type": "command",
1020 "command": "/path/to/idle-notification.sh"
1021 }
1022 ]
1023 }
1024 ]
1025 }
1026}
1027```
1028
1029#### Notification input
1030
1031In 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.
512 1032
513```json theme={null}1033```json theme={null}
514{1034{
518 "permission_mode": "default",1038 "permission_mode": "default",
519 "hook_event_name": "Notification",1039 "hook_event_name": "Notification",
520 "message": "Claude needs your permission to use Bash",1040 "message": "Claude needs your permission to use Bash",
1041 "title": "Permission needed",
521 "notification_type": "permission_prompt"1042 "notification_type": "permission_prompt"
522}1043}
523```1044```
524 1045
525### UserPromptSubmit Input1046Notification 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:
1047
1048| Field | Description |
1049| :------------------ | :------------------------------- |
1050| `additionalContext` | String added to Claude's context |
1051
1052### SubagentStart
1053
1054Runs 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/`).
1055
1056#### SubagentStart input
1057
1058In 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).
526 1059
527```json theme={null}1060```json theme={null}
528{1061{
530 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1063 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
531 "cwd": "/Users/...",1064 "cwd": "/Users/...",
532 "permission_mode": "default",1065 "permission_mode": "default",
533 "hook_event_name": "UserPromptSubmit",1066 "hook_event_name": "SubagentStart",
534 "prompt": "Write a function to calculate the factorial of a number"1067 "agent_id": "agent-abc123",
1068 "agent_type": "Explore"
535}1069}
536```1070```
537 1071
538### Stop and SubagentStop Input1072SubagentStart 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:
539 1073
540`stop_hook_active` is true when Claude Code is already continuing as a result of1074| Field | Description |
541a stop hook. Check this value or process the transcript to prevent Claude Code1075| :------------------ | :------------------------------------- |
542from running indefinitely.1076| `additionalContext` | String added to the subagent's context |
543 1077
544```json theme={null}1078```json theme={null}
545{1079{
546 "session_id": "abc123",1080 "hookSpecificOutput": {
547 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1081 "hookEventName": "SubagentStart",
548 "permission_mode": "default",1082 "additionalContext": "Follow security guidelines for this task"
549 "hook_event_name": "Stop",1083 }
550 "stop_hook_active": true
551}1084}
552```1085```
553 1086
554### PreCompact Input1087### SubagentStop
1088
1089Runs when a Claude Code subagent has finished responding. Matches on agent type, same values as SubagentStart.
1090
1091#### SubagentStop input
555 1092
556For `manual`, `custom_instructions` comes from what the user passes into1093In addition to the [common input fields](#common-input-fields), SubagentStop hooks receive `stop_hook_active`, `agent_id`, `agent_type`, `agent_transcript_path`, and `last_assistant_message`. The `agent_type` field is the value used for matcher filtering. The `transcript_path` is the main session's transcript, while `agent_transcript_path` is the subagent's own transcript stored in a nested `subagents/` folder. The `last_assistant_message` field contains the text content of the subagent's final response, so hooks can access it without parsing the transcript file.
557`/compact`. For `auto`, `custom_instructions` is empty.
558 1094
559```json theme={null}1095```json theme={null}
560{1096{
561 "session_id": "abc123",1097 "session_id": "abc123",
562 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1098 "transcript_path": "~/.claude/projects/.../abc123.jsonl",
1099 "cwd": "/Users/...",
563 "permission_mode": "default",1100 "permission_mode": "default",
564 "hook_event_name": "PreCompact",1101 "hook_event_name": "SubagentStop",
565 "trigger": "manual",1102 "stop_hook_active": false,
566 "custom_instructions": ""1103 "agent_id": "def456",
1104 "agent_type": "Explore",
1105 "agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl",
1106 "last_assistant_message": "Analysis complete. Found 3 potential issues..."
567}1107}
568```1108```
569 1109
570### SessionStart Input1110SubagentStop hooks use the same decision control format as [Stop hooks](#stop-decision-control).
1111
1112### Stop
1113
1114Runs when the main Claude Code agent has finished responding. Does not run if
1115the stoppage occurred due to a user interrupt.
1116
1117#### Stop input
1118
1119In addition to the [common input fields](#common-input-fields), Stop hooks receive `stop_hook_active` and `last_assistant_message`. The `stop_hook_active` field is `true` when Claude Code is already continuing as a result of a stop hook. Check this value or process the transcript to prevent Claude Code from running indefinitely. The `last_assistant_message` field contains the text content of Claude's final response, so hooks can access it without parsing the transcript file.
571 1120
572```json theme={null}1121```json theme={null}
573{1122{
574 "session_id": "abc123",1123 "session_id": "abc123",
575 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1124 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1125 "cwd": "/Users/...",
576 "permission_mode": "default",1126 "permission_mode": "default",
577 "hook_event_name": "SessionStart",1127 "hook_event_name": "Stop",
578 "source": "startup"1128 "stop_hook_active": true,
1129 "last_assistant_message": "I've completed the refactoring. Here's a summary..."
1130}
1131```
1132
1133#### Stop decision control
1134
1135`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:
1136
1137| Field | Description |
1138| :--------- | :------------------------------------------------------------------------- |
1139| `decision` | `"block"` prevents Claude from stopping. Omit to allow Claude to stop |
1140| `reason` | Required when `decision` is `"block"`. Tells Claude why it should continue |
1141
1142```json theme={null}
1143{
1144 "decision": "block",
1145 "reason": "Must be provided when Claude is blocked from stopping"
579}1146}
580```1147```
581 1148
582### SessionEnd Input1149### TeammateIdle
1150
1151Runs when an [agent team](/en/agent-teams) teammate is about to go idle after finishing its turn. Use this to enforce quality gates before a teammate stops working, such as requiring passing lint checks or verifying that output files exist.
1152
1153When a `TeammateIdle` hook exits with code 2, the teammate receives the stderr message as feedback and continues working instead of going idle. TeammateIdle hooks do not support matchers and fire on every occurrence.
1154
1155#### TeammateIdle input
1156
1157In addition to the [common input fields](#common-input-fields), TeammateIdle hooks receive `teammate_name` and `team_name`.
583 1158
584```json theme={null}1159```json theme={null}
585{1160{
586 "session_id": "abc123",1161 "session_id": "abc123",
587 "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",1162 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
588 "cwd": "/Users/...",1163 "cwd": "/Users/...",
589 "permission_mode": "default",1164 "permission_mode": "default",
590 "hook_event_name": "SessionEnd",1165 "hook_event_name": "TeammateIdle",
591 "reason": "exit"1166 "teammate_name": "researcher",
1167 "team_name": "my-project"
592}1168}
593```1169```
594 1170
595## Hook Output1171| Field | Description |
1172| :-------------- | :-------------------------------------------- |
1173| `teammate_name` | Name of the teammate that is about to go idle |
1174| `team_name` | Name of the team |
596 1175
597There are two mutually exclusive ways for hooks to return output back to Claude Code. The output1176#### TeammateIdle decision control
598communicates whether to block and any feedback that should be shown to Claude
599and the user.
600 1177
601### Simple: Exit Code1178TeammateIdle hooks use exit codes only, not JSON decision control. This example checks that a build artifact exists before allowing a teammate to go idle:
602 1179
603Hooks communicate status through exit codes, stdout, and stderr:1180```bash theme={null}
604 1181#!/bin/bash
605* **Exit code 0**: Success. `stdout` is shown to the user in verbose mode
606 (ctrl+o), except for `UserPromptSubmit` and `SessionStart`, where stdout is
607 added to the context. JSON output in `stdout` is parsed for structured control
608 (see [Advanced: JSON Output](#advanced-json-output)).
609* **Exit code 2**: Blocking error. Only `stderr` is used as the error message
610 and fed back to Claude. The format is `[command]: {stderr}`. JSON in `stdout`
611 is **not** processed for exit code 2. See per-hook-event behavior below.
612* **Other exit codes**: Non-blocking error. `stderr` is shown to the user in verbose mode (ctrl+o) with
613 format `Failed with non-blocking status code: {stderr}`. If `stderr` is empty,
614 it shows `No stderr output`. Execution continues.
615
616<Warning>
617 Reminder: Claude Code does not see stdout if the exit code is 0, except for
618 the `UserPromptSubmit` hook where stdout is injected as context.
619</Warning>
620 1182
621#### Exit Code 2 Behavior1183if [ ! -f "./dist/output.js" ]; then
1184 echo "Build artifact missing. Run the build before stopping." >&2
1185 exit 2
1186fi
622 1187
623| Hook Event | Behavior |1188exit 0
624| ------------------- | ------------------------------------------------------------------ |1189```
625| `PreToolUse` | Blocks the tool call, shows stderr to Claude |
626| `PermissionRequest` | Denies the permission, shows stderr to Claude |
627| `PostToolUse` | Shows stderr to Claude (tool already ran) |
628| `Notification` | N/A, shows stderr to user only |
629| `UserPromptSubmit` | Blocks prompt processing, erases prompt, shows stderr to user only |
630| `Stop` | Blocks stoppage, shows stderr to Claude |
631| `SubagentStop` | Blocks stoppage, shows stderr to Claude subagent |
632| `PreCompact` | N/A, shows stderr to user only |
633| `SessionStart` | N/A, shows stderr to user only |
634| `SessionEnd` | N/A, shows stderr to user only |
635 1190
636### Advanced: JSON Output1191### TaskCompleted
637 1192
638Hooks can return structured JSON in `stdout` for more sophisticated control.1193Runs when a task is being marked as completed. This fires in two situations: when any agent explicitly marks a task as completed through the TaskUpdate tool, or when an [agent team](/en/agent-teams) teammate finishes its turn with in-progress tasks. Use this to enforce completion criteria like passing tests or lint checks before a task can close.
639 1194
640<Warning>1195When a `TaskCompleted` hook exits with code 2, the task is not marked as completed and the stderr message is fed back to the model as feedback. TaskCompleted hooks do not support matchers and fire on every occurrence.
641 JSON output is only processed when the hook exits with code 0. If your hook
642 exits with code 2 (blocking error), `stderr` text is used directly—any JSON in `stdout`
643 is ignored. For other non-zero exit codes, only `stderr` is shown to the user in verbose mode (ctrl+o).
644</Warning>
645 1196
646#### Common JSON Fields1197#### TaskCompleted input
647 1198
648All hook types can include these optional fields:1199In addition to the [common input fields](#common-input-fields), TaskCompleted hooks receive `task_id`, `task_subject`, and optionally `task_description`, `teammate_name`, and `team_name`.
649 1200
650```json theme={null}1201```json theme={null}
651{1202{
652 "continue": true, // Whether Claude should continue after hook execution (default: true)1203 "session_id": "abc123",
653 "stopReason": "string", // Message shown when continue is false1204 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
654 1205 "cwd": "/Users/...",
655 "suppressOutput": true, // Hide stdout from transcript mode (default: false)1206 "permission_mode": "default",
656 "systemMessage": "string" // Optional warning message shown to the user1207 "hook_event_name": "TaskCompleted",
1208 "task_id": "task-001",
1209 "task_subject": "Implement user authentication",
1210 "task_description": "Add login and signup endpoints",
1211 "teammate_name": "implementer",
1212 "team_name": "my-project"
657}1213}
658```1214```
659 1215
660If `continue` is false, Claude stops processing after the hooks run.1216| Field | Description |
1217| :----------------- | :------------------------------------------------------ |
1218| `task_id` | Identifier of the task being completed |
1219| `task_subject` | Title of the task |
1220| `task_description` | Detailed description of the task. May be absent |
1221| `teammate_name` | Name of the teammate completing the task. May be absent |
1222| `team_name` | Name of the team. May be absent |
1223
1224#### TaskCompleted decision control
1225
1226TaskCompleted hooks use exit codes only, not JSON decision control. This example runs tests and blocks task completion if they fail:
1227
1228```bash theme={null}
1229#!/bin/bash
1230INPUT=$(cat)
1231TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject')
1232
1233# Run the test suite
1234if ! npm test 2>&1; then
1235 echo "Tests not passing. Fix failing tests before completing: $TASK_SUBJECT" >&2
1236 exit 2
1237fi
661 1238
662* For `PreToolUse`, this is different from `"permissionDecision": "deny"`, which1239exit 0
663 only blocks a specific tool call and provides automatic feedback to Claude.1240```
664* For `PostToolUse`, this is different from `"decision": "block"`, which
665 provides automated feedback to Claude.
666* For `UserPromptSubmit`, this prevents the prompt from being processed.
667* For `Stop` and `SubagentStop`, this takes precedence over any
668 `"decision": "block"` output.
669* In all cases, `"continue" = false` takes precedence over any
670 `"decision": "block"` output.
671 1241
672`stopReason` accompanies `continue` with a reason shown to the user, not shown1242### ConfigChange
673to Claude.
674 1243
675#### `PreToolUse` Decision Control1244Runs when a configuration file changes during a session. Use this to audit settings changes, enforce security policies, or block unauthorized modifications to configuration files.
676 1245
677`PreToolUse` hooks can control whether a tool call proceeds.1246ConfigChange hooks fire for changes to settings files, managed policy settings, and skill files. The `source` field in the input tells you which type of configuration changed, and the optional `file_path` field provides the path to the changed file.
678 1247
679* `"allow"` bypasses the permission system. `permissionDecisionReason` is shown1248The matcher filters on the configuration source:
680 to the user but not to Claude.
681* `"deny"` prevents the tool call from executing. `permissionDecisionReason` is
682 shown to Claude.
683* `"ask"` asks the user to confirm the tool call in the UI.
684 `permissionDecisionReason` is shown to the user but not to Claude.
685 1249
686Additionally, hooks can modify tool inputs before execution using `updatedInput`:1250| Matcher | When it fires |
1251| :----------------- | :---------------------------------------- |
1252| `user_settings` | `~/.claude/settings.json` changes |
1253| `project_settings` | `.claude/settings.json` changes |
1254| `local_settings` | `.claude/settings.local.json` changes |
1255| `policy_settings` | Managed policy settings change |
1256| `skills` | A skill file in `.claude/skills/` changes |
687 1257
688* `updatedInput` allows you to modify the tool's input parameters before the tool executes.1258This example logs all configuration changes for security auditing:
689* This is most useful with `"permissionDecision": "allow"` to modify and approve tool calls.
690 1259
691```json theme={null}1260```json theme={null}
692{1261{
693 "hookSpecificOutput": {1262 "hooks": {
694 "hookEventName": "PreToolUse",1263 "ConfigChange": [
695 "permissionDecision": "allow"1264 {
696 "permissionDecisionReason": "My reason here",1265 "hooks": [
697 "updatedInput": {1266 {
698 "field_to_modify": "new value"1267 "type": "command",
1268 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/audit-config-change.sh"
1269 }
1270 ]
699 }1271 }
1272 ]
700 }1273 }
701}1274}
702```1275```
703 1276
704<Note>1277#### ConfigChange input
705 The `decision` and `reason` fields are deprecated for PreToolUse hooks.
706 Use `hookSpecificOutput.permissionDecision` and
707 `hookSpecificOutput.permissionDecisionReason` instead. The deprecated fields
708 `"approve"` and `"block"` map to `"allow"` and `"deny"` respectively.
709</Note>
710
711#### `PermissionRequest` Decision Control
712
713`PermissionRequest` hooks can allow or deny permission requests shown to the user.
714 1278
715* For `"behavior": "allow"` you can also optionally pass in an `"updatedInput"` that modifies the tool's input parameters before the tool executes.1279In addition to the [common input fields](#common-input-fields), ConfigChange hooks receive `source` and optionally `file_path`. The `source` field indicates which configuration type changed, and `file_path` provides the path to the specific file that was modified.
716* 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.
717 1280
718```json theme={null}1281```json theme={null}
719{1282{
720 "hookSpecificOutput": {1283 "session_id": "abc123",
721 "hookEventName": "PermissionRequest",1284 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
722 "decision": {1285 "cwd": "/Users/...",
723 "behavior": "allow",1286 "permission_mode": "default",
724 "updatedInput": {1287 "hook_event_name": "ConfigChange",
725 "command": "npm run lint"1288 "source": "project_settings",
726 }1289 "file_path": "/Users/.../my-project/.claude/settings.json"
727 }
728 }
729}1290}
730```1291```
731 1292
732#### `PostToolUse` Decision Control1293#### ConfigChange decision control
733 1294
734`PostToolUse` hooks can provide feedback to Claude after tool execution.1295ConfigChange hooks can block configuration changes from taking effect. Use exit code 2 or a JSON `decision` to prevent the change. When blocked, the new settings are not applied to the running session.
735 1296
736* `"block"` automatically prompts Claude with `reason`.1297| Field | Description |
737* `undefined` does nothing. `reason` is ignored.1298| :--------- | :--------------------------------------------------------------------------------------- |
738* `"hookSpecificOutput.additionalContext"` adds context for Claude to consider.1299| `decision` | `"block"` prevents the configuration change from being applied. Omit to allow the change |
1300| `reason` | Explanation shown to the user when `decision` is `"block"` |
739 1301
740```json theme={null}1302```json theme={null}
741{1303{
742 "decision": "block" | undefined,1304 "decision": "block",
743 "reason": "Explanation for decision",1305 "reason": "Configuration changes to project settings require admin approval"
744 "hookSpecificOutput": {
745 "hookEventName": "PostToolUse",
746 "additionalContext": "Additional information for Claude"
747 }
748}1306}
749```1307```
750 1308
751#### `UserPromptSubmit` Decision Control1309`policy_settings` changes cannot be blocked. Hooks still fire for `policy_settings` sources, so you can use them for audit logging, but any blocking decision is ignored. This ensures enterprise-managed settings always take effect.
752
753`UserPromptSubmit` hooks can control whether a user prompt is processed and add context.
754 1310
755**Adding context (exit code 0):**1311### PreCompact
756There are two ways to add context to the conversation:
757 1312
7581. **Plain text stdout** (simpler): Any non-JSON text written to stdout is added1313Runs before Claude Code is about to run a compact operation.
759 as context. This is the easiest way to inject information.
760 1314
7612. **JSON with `additionalContext`** (structured): Use the JSON format below for1315The matcher value indicates whether compaction was triggered manually or automatically:
762 more control. The `additionalContext` field is added as context.
763 1316
764Both methods work with exit code 0. Plain stdout is shown as hook output in1317| Matcher | When it fires |
765the transcript; `additionalContext` is added more discretely.1318| :------- | :------------------------------------------- |
1319| `manual` | `/compact` |
1320| `auto` | Auto-compact when the context window is full |
766 1321
767**Blocking prompts:**1322#### PreCompact input
768 1323
769* `"decision": "block"` prevents the prompt from being processed. The submitted1324In 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.
770 prompt is erased from context. `"reason"` is shown to the user but not added
771 to context.
772* `"decision": undefined` (or omitted) allows the prompt to proceed normally.
773 1325
774```json theme={null}1326```json theme={null}
775{1327{
776 "decision": "block" | undefined,1328 "session_id": "abc123",
777 "reason": "Explanation for decision",1329 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
778 "hookSpecificOutput": {1330 "cwd": "/Users/...",
779 "hookEventName": "UserPromptSubmit",1331 "permission_mode": "default",
780 "additionalContext": "My additional context here"1332 "hook_event_name": "PreCompact",
781 }1333 "trigger": "manual",
1334 "custom_instructions": ""
782}1335}
783```1336```
784 1337
785<Note>1338### SessionEnd
786 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 to1339
787 block prompts or want more structured control.1340Runs when a Claude Code session ends. Useful for cleanup tasks, logging session
788</Note>1341statistics, or saving session state. Supports matchers to filter by exit reason.
1342
1343The `reason` field in the hook input indicates why the session ended:
789 1344
790#### `Stop`/`SubagentStop` Decision Control1345| Reason | Description |
1346| :---------------------------- | :----------------------------------------- |
1347| `clear` | Session cleared with `/clear` command |
1348| `logout` | User logged out |
1349| `prompt_input_exit` | User exited while prompt input was visible |
1350| `bypass_permissions_disabled` | Bypass permissions mode was disabled |
1351| `other` | Other exit reasons |
791 1352
792`Stop` and `SubagentStop` hooks can control whether Claude must continue.1353#### SessionEnd input
793 1354
794* `"block"` prevents Claude from stopping. You must populate `reason` for Claude1355In 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.
795 to know how to proceed.
796* `undefined` allows Claude to stop. `reason` is ignored.
797 1356
798```json theme={null}1357```json theme={null}
799{1358{
800 "decision": "block" | undefined,1359 "session_id": "abc123",
801 "reason": "Must be provided when Claude is blocked from stopping"1360 "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
1361 "cwd": "/Users/...",
1362 "permission_mode": "default",
1363 "hook_event_name": "SessionEnd",
1364 "reason": "other"
802}1365}
803```1366```
804 1367
805#### `SessionStart` Decision Control1368SessionEnd hooks have no decision control. They cannot block session termination but can perform cleanup tasks.
1369
1370## Prompt-based hooks
1371
1372In 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`, `SubagentStop`, and `TaskCompleted`. `TeammateIdle` does not support prompt-based or agent-based hooks.
806 1373
807`SessionStart` hooks allow you to load in context at the start of a session.1374### How prompt-based hooks work
1375
1376Instead of executing a Bash command, prompt-based hooks:
1377
13781. Send the hook input and your prompt to a Claude model, Haiku by default
13792. The LLM responds with structured JSON containing a decision
13803. Claude Code processes the decision automatically
1381
1382### Prompt hook configuration
808 1383
809* `"hookSpecificOutput.additionalContext"` adds the string to the context.1384Set `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.
810* Multiple hooks' `additionalContext` values are concatenated.1385
1386This `Stop` hook asks the LLM to evaluate whether all tasks are complete before allowing Claude to finish:
811 1387
812```json theme={null}1388```json theme={null}
813{1389{
814 "hookSpecificOutput": {1390 "hooks": {
815 "hookEventName": "SessionStart",1391 "Stop": [
816 "additionalContext": "My additional context here"1392 {
1393 "hooks": [
1394 {
1395 "type": "prompt",
1396 "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."
1397 }
1398 ]
1399 }
1400 ]
817 }1401 }
818}1402}
819```1403```
820 1404
821#### `SessionEnd` Decision Control1405| Field | Required | Description |
822 1406| :-------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
823`SessionEnd` hooks run when a session ends. They cannot block session termination1407| `type` | yes | Must be `"prompt"` |
824but can perform cleanup tasks.1408| `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 |
825 1409| `model` | no | Model to use for evaluation. Defaults to a fast model |
826#### Exit Code Example: Bash Command Validation1410| `timeout` | no | Timeout in seconds. Default: 30 |
827
828```python theme={null}
829#!/usr/bin/env python3
830import json
831import re
832import sys
833
834# Define validation rules as a list of (regex pattern, message) tuples
835VALIDATION_RULES = [
836 (
837 r"\bgrep\b(?!.*\|)",
838 "Use 'rg' (ripgrep) instead of 'grep' for better performance and features",
839 ),
840 (
841 r"\bfind\s+\S+\s+-name\b",
842 "Use 'rg --files | rg pattern' or 'rg --files -g pattern' instead of 'find -name' for better performance",
843 ),
844]
845
846
847def validate_command(command: str) -> list[str]:
848 issues = []
849 for pattern, message in VALIDATION_RULES:
850 if re.search(pattern, command):
851 issues.append(message)
852 return issues
853
854
855try:
856 input_data = json.load(sys.stdin)
857except json.JSONDecodeError as e:
858 print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
859 sys.exit(1)
860
861tool_name = input_data.get("tool_name", "")
862tool_input = input_data.get("tool_input", {})
863command = tool_input.get("command", "")
864
865if tool_name != "Bash" or not command:
866 sys.exit(1)
867
868# Validate the command
869issues = validate_command(command)
870
871if issues:
872 for message in issues:
873 print(f"• {message}", file=sys.stderr)
874 # Exit code 2 blocks tool call and shows stderr to Claude
875 sys.exit(2)
876```
877
878#### JSON Output Example: UserPromptSubmit to Add Context and Validation
879 1411
880<Note>1412### Response schema
881 For `UserPromptSubmit` hooks, you can inject context using either method:
882 1413
883 * **Plain text stdout** with exit code 0: Simplest approach, prints text1414The LLM must respond with JSON containing:
884 * **JSON output** with exit code 0: Use `"decision": "block"` to reject prompts,
885 or `additionalContext` for structured context injection
886 1415
887 Remember: Exit code 2 only uses `stderr` for the error message. To block using1416```json theme={null}
888 JSON (with a custom reason), use `"decision": "block"` with exit code 0.1417{
889</Note>1418 "ok": true | false,
1419 "reason": "Explanation for the decision"
1420}
1421```
890 1422
891```python theme={null}1423| Field | Description |
892#!/usr/bin/env python31424| :------- | :--------------------------------------------------------- |
893import json1425| `ok` | `true` allows the action, `false` prevents it |
894import sys1426| `reason` | Required when `ok` is `false`. Explanation shown to Claude |
895import re
896import datetime
897
898# Load input from stdin
899try:
900 input_data = json.load(sys.stdin)
901except json.JSONDecodeError as e:
902 print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
903 sys.exit(1)
904
905prompt = input_data.get("prompt", "")
906
907# Check for sensitive patterns
908sensitive_patterns = [
909 (r"(?i)\b(password|secret|key|token)\s*[:=]", "Prompt contains potential secrets"),
910]
911
912for pattern, message in sensitive_patterns:
913 if re.search(pattern, prompt):
914 # Use JSON output to block with a specific reason
915 output = {
916 "decision": "block",
917 "reason": f"Security policy violation: {message}. Please rephrase your request without sensitive information."
918 }
919 print(json.dumps(output))
920 sys.exit(0)
921 1427
922# Add current time to context1428### Example: Multi-criteria Stop hook
923context = f"Current time: {datetime.datetime.now()}"
924print(context)
925 1429
926"""1430This `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:
927The following is also equivalent:
928print(json.dumps({
929 "hookSpecificOutput": {
930 "hookEventName": "UserPromptSubmit",
931 "additionalContext": context,
932 },
933}))
934"""
935 1431
936# Allow the prompt to proceed with the additional context1432```json theme={null}
937sys.exit(0)1433{
1434 "hooks": {
1435 "Stop": [
1436 {
1437 "hooks": [
1438 {
1439 "type": "prompt",
1440 "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.",
1441 "timeout": 30
1442 }
1443 ]
1444 }
1445 ]
1446 }
1447}
938```1448```
939 1449
940#### JSON Output Example: PreToolUse with Approval1450## Agent-based hooks
941
942```python theme={null}
943#!/usr/bin/env python3
944import json
945import sys
946
947# Load input from stdin
948try:
949 input_data = json.load(sys.stdin)
950except json.JSONDecodeError as e:
951 print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
952 sys.exit(1)
953
954tool_name = input_data.get("tool_name", "")
955tool_input = input_data.get("tool_input", {})
956
957# Example: Auto-approve file reads for documentation files
958if tool_name == "Read":
959 file_path = tool_input.get("file_path", "")
960 if file_path.endswith((".md", ".mdx", ".txt", ".json")):
961 # Use JSON output to auto-approve the tool call
962 output = {
963 "decision": "approve",
964 "reason": "Documentation file auto-approved",
965 "suppressOutput": True # Don't show in verbose mode
966 }
967 print(json.dumps(output))
968 sys.exit(0)
969 1451
970# For other cases, let the normal permission flow proceed1452Agent-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.
971sys.exit(0)1453
972```1454### How agent hooks work
973 1455
974## Working with MCP Tools1456When an agent hook fires:
975 1457
976Claude Code hooks work seamlessly with14581. Claude Code spawns a subagent with your prompt and the hook's JSON input
977[Model Context Protocol (MCP) tools](/en/mcp). When MCP servers14592. The subagent can use tools like Read, Grep, and Glob to investigate
978provide tools, they appear with a special naming pattern that you can match in14603. After up to 50 turns, the subagent returns a structured `{ "ok": true/false }` decision
979your hooks.14614. Claude Code processes the decision the same way as a prompt hook
980 1462
981### MCP Tool Naming1463Agent hooks are useful when verification requires inspecting actual files or test output, not just evaluating the hook input data alone.
982 1464
983MCP tools follow the pattern `mcp__<server>__<tool>`, for example:1465### Agent hook configuration
984 1466
985* `mcp__memory__create_entities` - Memory server's create entities tool1467Set `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:
986* `mcp__filesystem__read_file` - Filesystem server's read file tool
987* `mcp__github__search_repositories` - GitHub server's search tool
988 1468
989### Configuring Hooks for MCP Tools1469| Field | Required | Description |
1470| :-------- | :------- | :------------------------------------------------------------------------------------------ |
1471| `type` | yes | Must be `"agent"` |
1472| `prompt` | yes | Prompt describing what to verify. Use `$ARGUMENTS` as a placeholder for the hook input JSON |
1473| `model` | no | Model to use. Defaults to a fast model |
1474| `timeout` | no | Timeout in seconds. Default: 60 |
990 1475
991You can target specific MCP tools or entire MCP servers:1476The response schema is the same as prompt hooks: `{ "ok": true }` to allow or `{ "ok": false, "reason": "..." }` to block.
1477
1478This `Stop` hook verifies that all unit tests pass before allowing Claude to finish:
992 1479
993```json theme={null}1480```json theme={null}
994{1481{
995 "hooks": {1482 "hooks": {
996 "PreToolUse": [1483 "Stop": [
997 {1484 {
998 "matcher": "mcp__memory__.*",
999 "hooks": [1485 "hooks": [
1000 {1486 {
1001 "type": "command",1487 "type": "agent",
1002 "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"1488 "prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
1489 "timeout": 120
1003 }1490 }
1004 ]1491 ]
1005 },1492 }
1493 ]
1494 }
1495}
1496```
1497
1498## Run hooks in the background
1499
1500By 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.
1501
1502### Configure an async hook
1503
1504Add `"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.
1505
1506This 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:
1507
1508```json theme={null}
1509{
1510 "hooks": {
1511 "PostToolUse": [
1006 {1512 {
1007 "matcher": "mcp__.*__write.*",1513 "matcher": "Write",
1008 "hooks": [1514 "hooks": [
1009 {1515 {
1010 "type": "command",1516 "type": "command",
1011 "command": "/home/user/scripts/validate-mcp-write.py"1517 "command": "/path/to/run-tests.sh",
1518 "async": true,
1519 "timeout": 120
1012 }1520 }
1013 ]1521 ]
1014 }1522 }
1017}1525}
1018```1526```
1019 1527
1020## Examples1528The `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.
1021
1022<Tip>
1023 For practical examples including code formatting, notifications, and file protection, see [More Examples](/en/hooks-guide#more-examples) in the get started guide.
1024</Tip>
1025
1026## Security Considerations
1027 1529
1028### Disclaimer1530### How async hooks execute
1029 1531
1030**USE AT YOUR OWN RISK**: Claude Code hooks execute arbitrary shell commands on1532When 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.
1031your system automatically. By using hooks, you acknowledge that:
1032 1533
1033* You are solely responsible for the commands you configure1534After 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.
1034* Hooks can modify, delete, or access any files your user account can access
1035* Malicious or poorly written hooks can cause data loss or system damage
1036* Anthropic provides no warranty and assumes no liability for any damages
1037 resulting from hook usage
1038* You should thoroughly test hooks in a safe environment before production use
1039 1535
1040Always review and understand any hook commands before adding them to your1536### Example: run tests after file changes
1041configuration.
1042 1537
1043### Security Best Practices1538This 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`:
1044 1539
1045Here are some key practices for writing more secure hooks:1540```bash theme={null}
1541#!/bin/bash
1542# run-tests-async.sh
1046 1543
10471. **Validate and sanitize inputs** - Never trust input data blindly1544# Read hook input from stdin
10482. **Always quote shell variables** - Use `"$VAR"` not `$VAR`1545INPUT=$(cat)
10493. **Block path traversal** - Check for `..` in file paths1546FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
10504. **Use absolute paths** - Specify full paths for scripts (use
1051 "\$CLAUDE\_PROJECT\_DIR" for the project path)
10525. **Skip sensitive files** - Avoid `.env`, `.git/`, keys, etc.
1053 1547
1054### Configuration Safety1548# Only run tests for source files
1549if [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.js ]]; then
1550 exit 0
1551fi
1055 1552
1056Direct edits to hooks in settings files don't take effect immediately. Claude1553# Run tests and report results via systemMessage
1057Code:1554RESULT=$(npm test 2>&1)
1555EXIT_CODE=$?
1058 1556
10591. Captures a snapshot of hooks at startup1557if [ $EXIT_CODE -eq 0 ]; then
10602. Uses this snapshot throughout the session1558 echo "{\"systemMessage\": \"Tests passed after editing $FILE_PATH\"}"
10613. Warns if hooks are modified externally1559else
10624. Requires review in `/hooks` menu for changes to apply1560 echo "{\"systemMessage\": \"Tests failed after editing $FILE_PATH: $RESULT\"}"
1561fi
1562```
1063 1563
1064This prevents malicious hook modifications from affecting your current session.1564Then add this configuration to `.claude/settings.json` in your project root. The `async: true` flag lets Claude keep working while tests run:
1065 1565
1066## Hook Execution Details1566```json theme={null}
1567{
1568 "hooks": {
1569 "PostToolUse": [
1570 {
1571 "matcher": "Write|Edit",
1572 "hooks": [
1573 {
1574 "type": "command",
1575 "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/run-tests-async.sh",
1576 "async": true,
1577 "timeout": 300
1578 }
1579 ]
1580 }
1581 ]
1582 }
1583}
1584```
1067 1585
1068* **Timeout**: 60-second execution limit by default, configurable per command.1586### Limitations
1069 * A timeout for an individual command does not affect the other commands.
1070* **Parallelization**: All matching hooks run in parallel
1071* **Deduplication**: Multiple identical hook commands are deduplicated automatically
1072* **Environment**: Runs in current directory with Claude Code's environment
1073 * The `CLAUDE_PROJECT_DIR` environment variable is available and contains the
1074 absolute path to the project root directory (where Claude Code was started)
1075 * 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.
1076* **Input**: JSON via stdin
1077* **Output**:
1078 * PreToolUse/PermissionRequest/PostToolUse/Stop/SubagentStop: Progress shown in verbose mode (ctrl+o)
1079 * Notification/SessionEnd: Logged to debug only (`--debug`)
1080 * UserPromptSubmit/SessionStart: stdout added as context for Claude
1081 1587
1082## Debugging1588Async hooks have several constraints compared to synchronous hooks:
1083 1589
1084### Basic Troubleshooting1590* Only `type: "command"` hooks support `async`. Prompt-based hooks cannot run asynchronously.
1591* Async hooks cannot block tool calls or return decisions. By the time the hook completes, the triggering action has already proceeded.
1592* Hook output is delivered on the next conversation turn. If the session is idle, the response waits until the next user interaction.
1593* Each execution creates a separate background process. There is no deduplication across multiple firings of the same async hook.
1085 1594
1086If your hooks aren't working:1595## Security considerations
1087 1596
10881. **Check configuration** - Run `/hooks` to see if your hook is registered1597### Disclaimer
10892. **Verify syntax** - Ensure your JSON settings are valid
10903. **Test commands** - Run hook commands manually first
10914. **Check permissions** - Make sure scripts are executable
10925. **Review logs** - Use `claude --debug` to see hook execution details
1093 1598
1094Common issues:1599Hooks run with your system user's full permissions.
1095 1600
1096* **Quotes not escaped** - Use `\"` inside JSON strings1601<Warning>
1097* **Wrong matcher** - Check tool names match exactly (case-sensitive)1602 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.
1098* **Command not found** - Use full paths for scripts1603</Warning>
1099 1604
1100### Advanced Debugging1605### Security best practices
1101 1606
1102For complex hook issues:1607Keep these practices in mind when writing hooks:
1103 1608
11041. **Inspect hook execution** - Use `claude --debug` to see detailed hook1609* **Validate and sanitize inputs**: never trust input data blindly
1105 execution1610* **Always quote shell variables**: use `"$VAR"` not `$VAR`
11062. **Validate JSON schemas** - Test hook input/output with external tools1611* **Block path traversal**: check for `..` in file paths
11073. **Check environment variables** - Verify Claude Code's environment is correct1612* **Use absolute paths**: specify full paths for scripts, using `"$CLAUDE_PROJECT_DIR"` for the project root
11084. **Test edge cases** - Try hooks with unusual file paths or inputs1613* **Skip sensitive files**: avoid `.env`, `.git/`, keys, etc.
11095. **Monitor system resources** - Check for resource exhaustion during hook
1110 execution
11116. **Use structured logging** - Implement logging in your hook scripts
1112 1614
1113### Debug Output Example1615## Debug hooks
1114 1616
1115Use `claude --debug` to see hook execution details:1617Run `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.
1116 1618
1117```1619```
1118[DEBUG] Executing hooks for PostToolUse:Write1620[DEBUG] Executing hooks for PostToolUse:Write
1120[DEBUG] Found 1 hook matchers in settings1622[DEBUG] Found 1 hook matchers in settings
1121[DEBUG] Matched 1 hooks for query "Write"1623[DEBUG] Matched 1 hooks for query "Write"
1122[DEBUG] Found 1 hook commands to execute1624[DEBUG] Found 1 hook commands to execute
1123[DEBUG] Executing hook command: <Your command> with timeout 60000ms1625[DEBUG] Executing hook command: <Your command> with timeout 600000ms
1124[DEBUG] Hook command completed with status 0: <Your stdout>1626[DEBUG] Hook command completed with status 0: <Your stdout>
1125```1627```
1126 1628
1127Progress messages appear in verbose mode (ctrl+o) showing:1629For 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.
1128
1129* Which hook is running
1130* Command being executed
1131* Success/failure status
1132* Output or error messages
1133
1134
1135
1136> To find navigation and other pages in this documentation, fetch the llms.txt file at: https://code.claude.com/docs/llms.txt