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