← Back to daily report

hooks.md

Changed on 2026-01-30 23:01:27 EST

+535 lines added
-511 lines removed
Visual Diff
> ## Documentation Index¶
> Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt¶
> Use this file to discover all available pages before exploring further.¶

# Hooks reference¶

>
This page provides reference documentation for implementing hooks in Claude Code.¶

<Tip>¶
For a quickstart guide with examples, see [Get started with Claude Code hooks
Reference for Claude Code hook events, configuration schema, JSON input/output formats, exit codes, async hooks, prompt hooks, and MCP tool hooks.¶

<Tip>¶
For a quickstart guide with examples, see [Automate workflows with hooks](/en/hooks-guide).¶
</Tip>¶

Hooks 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).¶
</Tip>
instead.

## Hook lifecycle¶

Hooks 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:

<div style={{maxWidth: "500px", margin: "0 auto"}}>¶
<Frame>¶
<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" />¶
</Frame>¶
</div>¶

| Hook | When it fires |¶
| :------------------- | :------------------------------ |¶
| `SessionStart` | Session begins or resumes |¶
| `UserPromptSubmit` | User submits a prompt |¶
| `PreToolUse` | Before tool execution |¶
| `PermissionRequest` | When permission dialog appears |¶
| `PostToolUse` | After tool succeeds |¶
| `PostToolUseFailure` | After tool fails |¶
| `SubagentStart` | When spawning a subagent |¶
| `SubagentStop` | When subagent finishes |¶
| `Stop` | Claude finishes responding |¶
| `PreCompact` | Before context compaction |¶
| `SessionEnd` | Session terminates |¶
| `Notification` | Claude Code sends notifications |¶

## Configuration¶

Claude Code hooks are configured in your [settings files](/en/settings):¶

* `~/.claude/settings.json` - User settings¶
* `.claude/settings.json` - Project settings¶
* `.claude/settings.local.json` - Local project settings (not committed)¶
* Managed policy settings¶

<Note>¶
Enterprise administrators can use `allowManagedHooksOnly` to block user, project, and plugin hooks. See [Hook configuration](/en/settings#hook-configuration).¶
</Note>¶

### Structure¶

Hooks are organized by matchers, where each matcher can have multiple hooks:¶

```json theme={null}¶
{¶
"hooks": {¶
"EventName": [¶
{¶
"matcher": "ToolPattern",¶
"hooks": [¶
{¶
"type": "command",¶
"command": "your-command-here"¶
}¶
]¶
}¶
]¶
}¶
}¶
```¶

* **matcher**: Pattern to match tool names, case-sensitive (only applicable for¶
`PreToolUse`, `PermissionRequest`, and `PostToolUse`)¶
* Simple strings match exactly: `Write` matches only the Write tool¶
* Supports regex: `Edit|Write` or `Notebook.*`¶
* Use `*` to match all tools. You can also use empty string (`""`) or leave¶
`matcher` blank.¶
* **hooks**: Array of hooks to execute when the pattern matches¶
* `type`: Hook execution type - `"command"` for bash commands or `"prompt"` for LLM-based evaluation¶
* `command`: (For `type: "command"`) The bash command to execute (can use `$CLAUDE_PROJECT_DIR` environment variable)¶
* `prompt`: (For `type: "prompt"`) The prompt to send to the LLM for evaluation¶
* `timeout`: (Optional) How long a hook should run, in seconds, before canceling that specific hook¶

For events like `UserPromptSubmit`, `Stop`, `SubagentStop`, and `Setup`¶
that don't use matchers, you can omit the matcher field:¶

```json theme={null}¶
{¶
"hooks": {¶
"UserPromptSubmit": [¶
{¶
"hooks": [¶
{¶
"type": "command",¶
"command": "/path/to/prompt-validator.py"¶
}¶
]¶
}¶
]¶
}¶
}¶
```¶

### Project-Specific Hook Scripts¶

You can use the environment variable `CLAUDE_PROJECT_DIR` (only available when¶
Claude Code spawns the hook command) to reference scripts stored in your project,¶
ensuring they work regardless of Claude's current directory:¶

```json theme={null}¶
{¶
"hooks": {¶
"PostToolUse": [¶
{¶
"matcher": "Write|Edit",¶
"hooks": [¶
{¶
"type": "command",¶
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-style.sh"¶
}¶
]¶
}¶
]¶
}¶
}¶
```¶

### Plugin hooks¶

[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.¶

**How plugin hooks work**:¶

* 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.¶
* When a plugin is enabled, its hooks are merged with user and project hooks¶
* Multiple hooks from different sources can respond to the same event¶
* Plugin hooks use the `${CLAUDE_PLUGIN_ROOT}` environment variable to reference plugin files¶

**Example plugin hook configuration**:¶

```json theme={null}¶
{¶
"description": "Automatic code formatting",¶
"hooks": {¶
"PostToolUse": [¶
{¶
"matcher": "Write|Edit",¶
"hooks": [¶
{¶
"type": "command",¶
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",¶
"timeout": 30¶
}¶
]¶
}¶
]¶
}¶
}¶
```¶

<Note>¶
Plugin hooks use the same format as regular hooks with an optional `description` field to explain the hook's purpose.¶
</Note>¶

<Note>¶
Plugin hooks run alongside your custom hooks. If multiple hooks match an event, they all execute in parallel.¶
</Note>¶

**Environment variables for plugins**:¶

* `${CLAUDE_PLUGIN_ROOT}`: Absolute path to the plugin directory¶
* `${CLAUDE_PROJECT_DIR}`: Project root directory (same as for project hooks)¶
* All standard environment variables are available¶

See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.¶

### Hooks in skills and agents¶

In 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.¶

**Supported events**: `PreToolUse`, `PostToolUse`, and `Stop`¶

**Example in a Skill**:¶

```yaml theme={null}¶
---¶
name: secure-operations¶
description: Perform operations with security checks¶
hooks:¶
PreToolUse:¶
- matcher: "Bash"¶
hooks:¶
- type: command¶
command: "./scripts/security-check.sh"¶
---¶
```¶

**Example in an agent**:¶

```yaml theme={null}¶
---¶
name: code-reviewer¶
description: Review code changes¶
hooks:¶
PostToolUse:¶
- matcher: "Edit|Write"¶
hooks:¶
- type: command¶
command: "./scripts/run-linter.sh"¶
---¶
```¶

Component-scoped hooks follow the same configuration format as settings-based hooks but are automatically cleaned up when the component finishes executing.¶

**Additional option for skills:**¶

* `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.¶

## Prompt-Based Hooks¶

In 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.¶

### How prompt-based hooks work¶

Instead of executing a bash command, prompt-based hooks:¶

1. Send the hook input and your prompt to a fast LLM (Haiku)¶
2. The LLM responds with structured JSON containing a decision¶
3. Claude Code processes the decision automatically¶

### Configuration¶

```json theme={null}¶
{¶
"hooks": {¶
"Stop": [¶
{¶
"hooks": [¶
{¶
"type": "prompt",¶
"prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."¶
}¶
]¶
}¶
]¶
}¶
}¶
```¶

**Fields:**¶

* `type`: Must be `"prompt"`¶
* `prompt`: 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¶
* `timeout`: (Optional) Timeout in seconds (default: 30 seconds)¶

### Response schema¶

The LLM must respond with JSON containing:¶

```json theme={null}¶
{¶
"ok": true | false,¶
"reason": "Explanation for the decision"¶
}¶
```¶

**Response fields:**¶

* `ok`: `true` allows the action, `false` prevents it¶
* `reason`: Required when `ok` is `false`. Explanation shown to Claude¶

### Supported hook events¶

Prompt-based hooks work with any hook event, but are most useful for:¶

* **Stop**: Intelligently decide if Claude should continue working¶
* **SubagentStop**: Evaluate if a subagent has completed its task¶
* **UserPromptSubmit**: Validate user prompts with LLM assistance¶
* **PreToolUse**: Make context-aware permission decisions¶
* **PermissionRequest**: Intelligently allow or deny permission dialogs¶

### Example: Intelligent Stop hook¶

```json theme={null}¶
{¶
"hooks": {¶
"Stop": [¶
{¶
"hooks": [¶
{¶
"type": "prompt",¶
"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.",¶
"timeout": 30¶
}¶
]¶
}¶
]¶
}¶
}¶
```¶

### Example: SubagentStop with custom logic¶

```json theme={null}¶
{¶
"hooks": {¶
"SubagentStop": [¶
{¶
"hooks": [¶
{¶
"type": "prompt",¶
"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."¶
}¶
]¶
}¶
]¶
}¶
}¶
```¶

### Comparison with bash command hooks¶

| Feature | Bash Command Hooks | Prompt-Based Hooks |¶
| --------------------- | ----------------------- | ------------------------------ |¶
| **Execution** | Runs bash script | Queries LLM |¶
| **Decision logic** | You implement in code | LLM evaluates context |¶
| **Setup complexity** | Requires script file | Configure prompt |¶
| **Context awareness** | Limited to script logic | Natural language understanding |¶
| **Performance** | Fast (local execution) | Slower (API call) |¶
| **Use case** | Deterministic rules | Context-aware decisions |¶

### Best practices¶

* **Be specific in prompts**: Clearly state what you want the LLM to evaluate¶
* **Include decision criteria**: List the factors the LLM should consider¶
* **Test your prompts**: Verify the LLM makes correct decisions for your use cases¶
* **Set appropriate timeouts**: Default is 30 seconds, adjust if needed¶
* **Use for complex decisions**: Bash hooks are better for simple, deterministic rules¶

See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.¶

## Hook Events¶

### PreToolUse¶

Runs after Claude creates tool parameters and before processing the tool call.¶

**Common matchers:**¶

* `Task` - Subagent tasks (see [subagents documentation](/en/sub-agents))¶
* `Bash` - Shell commands¶
* `Glob` - File pattern matching¶
* `Grep` - Content search¶
* `Read` - File reading¶
* `Edit` - File editing¶
* `Write` - File writing¶
* `WebFetch`, `WebSearch` - Web operations¶

Use [PreToolUse decision control](#pretooluse-decision-control) to allow, deny, or ask for permission to use the tool.¶

### PermissionRequest¶

Runs when the user is shown a permission dialog.¶
Use [PermissionRequest decision control](#permissionrequest-decision-control) to allow or deny on behalf of the user.¶

Recognizes the same matcher values as PreToolUse.¶

### PostToolUse¶

Runs immediately after a tool completes successfully.¶

Recognizes the same matcher values as PreToolUse.¶

### Notification¶

Runs when Claude Code sends notifications. Supports matchers to filter by notification type.¶

**Common matchers:**¶

* `permission_prompt` - Permission requests from Claude Code¶
* `idle_prompt` - When Claude is waiting for user input (after 60+ seconds of idle time)¶
* `auth_success` - Authentication success notifications¶
* `elicitation_dialog` - When Claude Code needs input for MCP tool elicitation¶

You can use matchers to run different hooks for different notification types, or omit the matcher to run hooks for all notifications.¶

**Example: Different notifications for different types**¶

```json theme={null}¶
{¶
"hooks": {¶
"Notification": [¶
{¶
"matcher": "permission_prompt",¶
"hooks": [¶
{¶
"type": "command",¶
"command": "/path/to/permission-alert.sh"¶
}¶
]¶
},¶
{¶
"matcher": "idle_prompt",¶
"hooks": [¶
{¶
"type": "command",¶
"command": "/path/to/idle-notification.sh"¶
}¶
]¶
}¶
]¶
}¶
}¶
```¶

### UserPromptSubmit¶

Runs when the user submits a prompt, before Claude processes it. This allows you¶
to add additional context based on the prompt/conversation, validate prompts, or¶
block certain types of prompts.¶

### Stop¶

Runs when the main Claude Code agent has finished responding. Does not run if¶
the stoppage occurred due to a user interrupt.¶

### SubagentStop¶

Runs when a Claude Code subagent (Task tool call) has finished responding.¶

### PreCompact¶

Runs before Claude Code is about to run a compact operation.¶

**Matchers:**¶

* `manual` - Invoked from `/compact`¶
* `auto` - Invoked from auto-compact (due to full context window)¶

### Setup¶

Runs 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.¶

<Note>¶
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.¶
</Note>¶

**Matchers:**¶

* `init` - Invoked from `--init` or `--init-only` flags¶
* `maintenance` - Invoked from `--maintenance` flag¶

Setup hooks have access to the `CLAUDE_ENV_FILE` environment variable for persisting environment variables, similar to SessionStart hooks.¶

### SessionStart¶

Runs when Claude Code starts a new session or resumes an existing session (which¶
currently 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.¶

<Note>¶
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.¶
</Note>¶

**Matchers:**¶

* `startup` - Invoked from startup¶
* `resume` - Invoked from `--resume`, `--continue`, or `/resume`¶
* `clear` - Invoked from `/clear`¶
* `compact` - Invoked from auto or manual compact.¶

#### Persisting environment variables¶

SessionStart 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.¶

**Example: Setting individual environment variables**¶

```bash theme={null}¶
#!/bin/bash¶

if [ -n "$CLAUDE_ENV_FILE" ]; then¶
echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"¶
echo 'export API_KEY=your-api-key' >> "$CLAUDE_ENV_FILE"¶
echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"¶
fi¶

exit 0¶
```¶

**Example: Persisting all environment changes from the hook**¶

When your setup modifies the environment (for example, `nvm use`), capture and persist all changes by diffing the environment:¶

```bash theme={null}¶
#!/bin/bash¶

ENV_BEFORE=$(export -p | sort)¶

# Run your setup commands that modify the environment¶
source ~/.nvm/nvm.sh¶
nvm use 20¶

if [ -n "$CLAUDE_ENV_FILE" ]; then¶
ENV_AFTER=$(export -p | sort)¶
comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"¶
fi¶

exit 0¶
```¶

Any variables written to this file will be available in all subsequent bash commands that Claude Code executes during the session.¶

<Note>¶
`CLAUDE_ENV_FILE` is only available for SessionStart hooks. Other hook types do not have access to this variable.¶
</Note>¶

### SessionEnd¶

Runs when a Claude Code session ends. Useful for cleanup tasks, logging session¶
statistics, or saving session state.¶

The `reason` field in the hook input will be one of:¶

* `clear` - Session cleared with /clear command¶
* `logout` - User logged out¶
* `prompt_input_exit` - User exited while prompt input was visible¶
* `other` - Other exit reasons¶

## Hook Input¶

Hooks receive JSON data via stdin containing session information and¶
event-specific data:¶

```typescript theme={null}¶
{¶
// Common fields¶
session_id: string¶
transcript_path: string // Path to conversation JSON¶
cwd: string // The current working directory when the hook is invoked¶
permission_mode: string // Current permission mode: "default", "plan", "acceptEdits", "dontAsk", or "bypassPermissions"¶

// Event-specific fields¶
hook_event_name: string¶
...¶
}¶
```¶

### PreToolUse Input¶

The exact schema for `tool_input` depends on the tool. Here are examples for commonly hooked tools.¶

#### Bash tool¶

The Bash tool is the most commonly hooked tool for command validation:¶

```json theme={null}¶
{¶
"session_id": "abc123",¶
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",¶
"cwd": "/Users/...",¶
"permission_mode": "default",¶
"hook_event_name": "PreToolUse",¶
"tool_name": "Bash",¶
"tool_input": {¶
"command": "psql -c 'SELECT * FROM users'",¶
"description": "Query the users table",¶
"timeout": 120000¶
},¶
"tool_use_id": "toolu_01ABC123..."¶
}¶
```¶

| Field | Type | Description |¶
| :------------------ | :------ | :-------------------------------------------- |¶
| `command` | string | The shell command to execute |¶
| `description` | string | Optional description of what the command does |¶
| `timeout` | number | Optional timeout in milliseconds |¶
| `run_in_background` | boolean | Whether to run the command in background |¶

#### Write tool¶

```json theme={null}¶
{¶
"session_id": "abc123",¶
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",¶
"cwd": "/Users/...",¶
"permission_mode": "default",¶
"hook_event_name": "PreToolUse",¶
"tool_name": "Write",¶
"tool_input": {¶
"file_path": "/path/to/file.txt",¶
"content": "file content"¶
},¶
"tool_use_id": "toolu_01ABC123..."¶
}¶
```¶

| Field | Type | Description |¶
| :---------- | :----- | :--------------------------------- |¶
| `file_path` | string | Absolute path to the file to write |¶
| `content` | string | Content to write to the file |¶

#### Edit tool¶

```json theme={null}¶
{¶
"session_id": "abc123",¶
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",¶
"cwd": "/Users/...",¶
"permission_mode": "default",¶
"hook_event_name": "PreToolUse",¶
"tool_name": "Edit",¶
"tool_input": {¶
"file_path": "/path/to/file.txt",¶
"old_string": "original text",¶
"new_string": "replacement text"¶
},¶
"tool_use_id": "toolu_01ABC123..."¶
}¶
```¶

| Field | Type | Description |¶
| :------------ | :------ | :-------------------------------------------------- |¶
| `file_path` | string | Absolute path to the file to edit |¶
| `old_string` | string | Text to find and replace |¶
| `new_string` | string | Replacement text |¶
| `replace_all` | boolean | Whether to replace all occurrences (default: false) |¶

#### Read tool¶

```json theme={null}¶
{¶
"session_id": "abc123",¶
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",¶
"cwd": "/Users/...",¶
"permission_mode": "default",¶
"hook_event_name": "PreToolUse",¶
"tool_name": "Read",¶
"tool_input": {¶
"file_path": "/path/to/file.txt"¶
},¶
"tool_use_id": "toolu_01ABC123..."¶
}¶
```¶

| Field | Type | Description |¶
| :---------- | :----- | :----------------------------------------- |¶
| `file_path` | string | Absolute path to the file to read |¶
| `offset` | number | Optional line number to start reading from |¶
| `limit` | number | Optional number of lines to read |¶

### PostToolUse Input¶

The exact schema for `tool_input` and `tool_response` depends on the tool.¶

```json theme={null}¶
{¶
"session_id": "abc123",¶
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",¶
"cwd": "/Users/...",¶
"permission_mode": "default",¶
"hook_event_name": "PostToolUse",¶
"tool_name": "Write",¶
"tool_input": {¶
"file_path": "/path/to/file.txt",¶
"content": "file content"¶
},¶
"tool_response": {¶
"filePath": "/path/to/file.txt",¶
"success": true¶
},¶
"tool_use_id": "toolu_01ABC123..."¶
}¶
```¶

### Notification Input¶

```json theme={null}¶
{¶
"session_id": "abc123",¶
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",¶
"cwd": "/Users/...",¶
"permission_mode": "default",¶
"hook_event_name": "Notification",¶
"message": "Claude needs your permission to use Bash",¶
"notification_type": "permission_prompt"¶
}¶
```¶

### UserPromptSubmit Input¶

```json theme={null}¶
{¶
"session_id": "abc123",¶
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",¶
"cwd": "/Users/...",¶
"permission_mode": "default",¶
"hook_event_name": "UserPromptSubmit",¶
"prompt": "Write a function to calculate the factorial of a number"¶
}¶
```¶

### Stop Input¶

`stop_hook_active` 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.¶

```json theme={null}¶
{¶
"session_id": "abc123",¶
"transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",¶
"cwd": "/Users/...",¶
"permission_mode": "default",¶
"hook_event_name": "Stop",¶
"stop_hook_active": true¶
}¶
```¶

### SubagentStop Input¶

Triggered 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.¶

```json theme={null}¶
{¶
"session_id": "abc123",¶
"transcript_path": "~/.claude/projects/.../abc123.jsonl",¶
"cwd": "/Users/...",¶
"permission_mode": "default",¶
"hook_event_name": "SubagentStop",¶
"stop_hook_active": false,¶
"agent_id": "def456",¶
"agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl"¶
}¶
```¶

### PreCompact Input¶

For `manual`, `custom_instructions` comes from what the user passes into¶
`/compact`. For `auto`, `custom_instructions` is empty.¶

```json theme={null}¶
{¶
"session_id": "abc123",¶
"transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",¶
"permission_mode": "default",¶
"hook_event_name": "PreCompact",¶
"trigger": "manual",¶
"custom_instructions": ""¶
}¶
```¶

### Setup Input¶

```json theme={null}¶
{¶
"session_id": "abc123",¶
"transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",¶
"cwd": "/Users/...",¶
"permission_mode": "default",¶
"hook_event_name": "Setup",¶
"trigger": "init"¶
}¶
```¶

The `trigger` field will be either `"init"` (from `--init` or `--init-only`) or `"maintenance"` (from `--maintenance`).¶

### SessionStart Input¶

```json theme={null}¶
{¶
"session_id": "abc123",¶
"transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",¶
"cwd": "/Users/...",¶
"permission_mode": "default",¶
"hook_event_name": "SessionStart",¶
"source": "startup",¶
"model": "claude-sonnet-4-20250514"¶
}¶
```¶

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 when available. If you start Claude Code with `claude --agent <name>`, an `agent_type` field contains the agent name.¶

### SubagentStart Input¶

```json theme={null}¶
{¶
"session_id": "abc123",¶
"transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",¶
"cwd": "/Users/...",¶
"permission_mode": "default",¶
"hook_event_name": "SubagentStart",¶
"agent_id": "agent-abc123",¶
"agent_type": "Explore"¶
}¶
```¶

Triggered 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).¶

### SessionEnd Input¶

```json theme={null}¶
{¶
"session_id": "abc123",¶
"transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",¶
"cwd": "/Users/...",¶
"permission_mode": "default",¶
"hook_event_name": "SessionEnd",¶
"reason": "exit"¶
}¶
```¶

## Hook Output¶

There are two mutually exclusive ways for hooks to return output back to Claude Code. The output¶
communicates whether to block and any feedback that should be shown to Claude¶
and the user.¶

### Simple: Exit Code¶

Hooks communicate status through exit codes, stdout, and stderr:¶

* **Exit code 0**: Success. `stdout` is shown to the user in verbose mode¶
(ctrl+o), except for `UserPromptSubmit` and `SessionStart`, where stdout is¶
added to the context. JSON output in `stdout` is parsed for structured control¶
(see [Advanced: JSON Output](#advanced-json-output)).¶
* **Exit code 2**: Blocking error. Only `stderr` is used as the error message¶
and fed back to Claude. The format is `[command]: {stderr}`. JSON in `stdout`¶
is **not** processed for exit code 2. See per-hook-event behavior below.¶
* **Other exit codes**: Non-blocking error. `stderr` is shown to the user in verbose mode (ctrl+o) with¶
format `Failed with non-blocking status code: {stderr}`. If `stderr` is empty,¶
it shows `No stderr output`. Execution continues.¶

<Warning>¶
Reminder: Claude Code does not see stdout if the exit code is 0, except for¶
the `UserPromptSubmit` hook where stdout is injected as context.¶
</Warning>¶

#### Exit Code 2 Behavior¶

| Hook Event | Behavior |¶
| ------------------- | ------------------------------------------------------------------ |¶
| `PreToolUse` | Blocks the tool call, shows stderr to Claude |¶
| `PermissionRequest` | Denies the permission, shows stderr to Claude |¶
| `PostToolUse` | Shows stderr to Claude (tool already ran) |¶
| `Notification` | N/A, shows stderr to user only |¶
| `UserPromptSubmit` | Blocks prompt processing, erases prompt, shows stderr to user only |¶
| `Stop` | Blocks stoppage, shows stderr to Claude |¶
| `SubagentStop` | Blocks stoppage, shows stderr to Claude subagent |¶
| `PreCompact` | N/A, shows stderr to user only |¶
| `Setup` | N/A, shows stderr to user only |¶
| `SessionStart` | N/A, shows stderr to user only |¶
| `SessionEnd` | N/A, shows stderr to user only |¶

### Advanced: JSON Output¶

Hooks can return structured JSON in `stdout` for more sophisticated control.¶

<Warning>¶
JSON output is only processed when the hook exits with code 0. If your hook¶
exits with code 2 (blocking error), `stderr` text is used directly—any JSON in `stdout`¶
is ignored. For other non-zero exit codes, only `stderr` is shown to the user in verbose mode (ctrl+o).¶
</Warning>¶

#### Common JSON Fields¶

All hook types can include these optional fields:¶

```json theme={null}¶
{¶
"continue": true, // Whether Claude should continue after hook execution (default: true)¶
"stopReason": "string", // Message shown when continue is false¶

"suppressOutput": true, // Hide stdout from transcript mode (default: false)¶
"systemMessage": "string" // Optional warning message shown to the user¶
}¶
```¶

If `continue` is false, Claude stops processing after the hooks run.¶

* For `PreToolUse`, this is different from `"permissionDecision": "deny"`, which¶
only blocks a specific tool call and provides automatic feedback to Claude.¶
* For `PostToolUse`, this is different from `"decision": "block"`, which¶
provides automated feedback to Claude.¶
* For `UserPromptSubmit`, this prevents the prompt from being processed.¶
* For `Stop` and `SubagentStop`, this takes precedence over any¶
`"decision": "block"` output.¶
* In all cases, `"continue" = false` takes precedence over any¶
`"decision": "block"` output.¶

`stopReason` accompanies `continue` with a reason shown to the user, not shown¶
to Claude.¶

#### `PreToolUse` Decision Control¶

`PreToolUse` hooks can control whether a tool call proceeds.¶

* `"allow"` bypasses the permission system. `permissionDecisionReason` is shown¶
to the user but not to Claude.¶
* `"deny"` prevents the tool call from executing. `permissionDecisionReason` is¶
shown to Claude.¶
* `"ask"` asks the user to confirm the tool call in the UI.¶
`permissionDecisionReason` is shown to the user but not to Claude.¶

Additionally, hooks can modify tool inputs before execution using `updatedInput`:¶

* `updatedInput` modifies the tool's input parameters before the tool executes¶
* Combine with `"permissionDecision": "allow"` to modify the input and auto-approve the tool call¶
* Combine with `"permissionDecision": "ask"` to modify the input and show it to the user for confirmation¶

Hooks can also provide context to Claude using `additionalContext`:¶

* `"hookSpecificOutput.additionalContext"` adds a string to Claude's context before the tool executes.¶

```json theme={null}¶
{¶
"hookSpecificOutput": {¶
"hookEventName": "PreToolUse",¶
"permissionDecision": "allow",¶
"permissionDecisionReason": "My reason here",¶
"updatedInput": {¶
"field_to_modify": "new value"¶
},¶
"additionalContext": "Current environment: production. Proceed with caution."¶
}¶
}¶
```¶

<Note>¶
The `decision` and `reason` fields are deprecated for PreToolUse hooks.¶
Use `hookSpecificOutput.permissionDecision` and¶
`hookSpecificOutput.permissionDecisionReason` instead. The deprecated fields¶
`"approve"` and `"block"` map to `"allow"` and `"deny"` respectively.¶
</Note>¶

#### `PermissionRequest` Decision Control¶

`PermissionRequest` hooks can allow or deny permission requests shown to the user.¶

* For `"behavior": "allow"` you can also optionally pass in an `"updatedInput"` that modifies the tool's input parameters before the tool executes.¶
* 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.¶

```json theme={null}¶
{¶
"hookSpecificOutput": {¶
"hookEventName": "PermissionRequest",¶
"decision": {¶
"behavior": "allow",¶
"updatedInput": {¶
"command": "npm run lint"¶
}¶
}¶
}¶
}¶
```¶

#### `PostToolUse` Decision Control¶

`PostToolUse` hooks can provide feedback to Claude after tool execution.¶

* `"block"` automatically prompts Claude with `reason`.¶
* `undefined` does nothing. `reason` is ignored.¶
* `"hookSpecificOutput.additionalContext"` adds context for Claude to consider.¶

```json theme={null}¶
{¶
"decision": "block" | undefined,¶
"reason": "Explanation for decision",¶
"hookSpecificOutput": {¶
"hookEventName": "PostToolUse",¶
"additionalContext": "Additional information for Claude"¶
}¶
}¶
```¶

#### `UserPromptSubmit` Decision Control¶

`UserPromptSubmit` hooks can control whether a user prompt is processed and add context.¶

**Adding context (exit code 0):**¶
There are two ways to add context to the conversation:¶

1. **Plain text stdout** (simpler): Any non-JSON text written to stdout is added¶
as context. This is the easiest way to inject information.¶

2. **JSON with `additionalContext`** (structured): Use the JSON format below for¶
more control. The `additionalContext` field is added as context.¶

Both methods work with exit code 0. Plain stdout is shown as hook output in¶
the transcript; `additionalContext` is added more discretely.¶

**Blocking prompts:**¶

* `"decision": "block"` prevents the prompt from being processed. The submitted¶
prompt is erased from context. `"reason"` is shown to the user but not added¶
to context.¶
* `"decision": undefined` (or omitted) allows the prompt to proceed normally.¶

```json theme={null}¶
{¶
"decision": "block" | undefined,¶
"reason": "Explanation for decision",¶
"hookSpecificOutput": {¶
"hookEventName": "UserPromptSubmit",¶
"additionalContext": "My additional context here"¶
}¶
}¶
```¶

<Note>¶
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¶
block prompts or want more structured control.¶
</Note>¶

#### `Stop`/`SubagentStop` Decision Control¶

`Stop` and `SubagentStop` hooks can control whether Claude must continue.¶

* `"block"` prevents Claude from stopping. You must populate `reason` for Claude¶
to know how to proceed.¶
* `undefined` allows Claude to stop. `reason` is ignored.¶

```json theme={null}¶
{¶
"decision": "block" | undefined,¶
"reason": "Must be provided when Claude is blocked from stopping"¶
}¶
```¶

#### `Setup` Decision Control¶

`Setup` hooks allow you to load context and configure the environment during repository initialization or maintenance.¶

* `"hookSpecificOutput.additionalContext"` adds the string to the context.¶
* Multiple hooks' `additionalContext` values are concatenated.¶
* Setup hooks have access to `CLAUDE_ENV_FILE` for persisting environment variables.¶

```json theme={null}¶
{¶
"hookSpecificOutput": {¶
"hookEventName": "Setup",¶
"additionalContext": "Repository initialized with custom configuration"¶
}¶
}¶
```¶

#### `SessionStart` Decision Control¶

`SessionStart` hooks allow you to load in context at the start of a session.¶

* `"hookSpecificOutput.additionalContext"` adds the string to the context.¶
* Multiple hooks' `additionalContext` values are concatenated.¶

```json theme={null}¶
{¶
"hookSpecificOutput": {¶
"hookEventName": "SessionStart",¶
"additionalContext": "My additional context here"¶
}¶
}¶
```¶

#### `SessionEnd` Decision Control¶

`SessionEnd` hooks run when a session ends. They cannot block session termination¶
but can perform cleanup tasks.¶

#### Exit Code Example: Bash Command Validation¶

```python theme={null}¶
#!/usr/bin/env python3¶
import json¶
import re¶
import sys¶

# Define validation rules as a list of (regex pattern, message) tuples¶
VALIDATION_RULES = [¶
(¶
r"\bgrep\b(?!.*\|)",¶
"Use 'rg' (ripgrep) instead of 'grep' for better performance and features",¶
),¶
(¶
r"\bfind\s+\S+\s+-name\b",¶
"Use 'rg --files | rg pattern' or 'rg --files -g pattern' instead of 'find -name' for better performance",¶
),¶
]¶


def validate_command(command: str) -> list[str]:¶
issues = []¶
for pattern, message in VALIDATION_RULES:¶
if re.search(pattern, command):¶
issues.append(message)¶
return issues¶


try:¶
input_data = json.load(sys.stdin)¶
except json.JSONDecodeError as e:¶
print(f"Error: Invalid JSON input: {e}", file=sys.stderr)¶
sys.exit(1)¶

tool_name = input_data.get("tool_name", "")¶
tool_input = input_data.get("tool_input", {})¶
command = tool_input.get("command", "")¶

if tool_name != "Bash" or not command:¶
sys.exit(1)¶

# Validate the command¶
issues = validate_command(command)¶

if issues:¶
for message in issues:¶
print(f"• {message}", file=sys.stderr)¶
# Exit code 2 blocks tool call and shows stderr to Claude¶
sys.exit(2)¶
```¶

#### JSON Output Example: UserPromptSubmit to Add Context and Validation¶

<Note>¶
For `UserPromptSubmit` hooks, you can inject context using either method:¶

* **Plain text stdout** with exit code 0: Simplest approach, prints text¶
* **JSON output** with exit code 0: Use `"decision": "block"` to reject prompts,¶
or `additionalContext` for structured context injection¶

Remember: Exit code 2 only uses `stderr` for the error message. To block using¶
JSON (with a custom reason), use `"decision": "block"` with exit code 0.¶
</Note>¶

```python theme={null}¶
#!/usr/bin/env python3¶
import json¶
import sys¶
import re¶
import datetime¶

# Load input from stdin¶
try:¶
input_data = json.load(sys.stdin)¶
except json.JSONDecodeError as e:¶
print(f"Error: Invalid JSON input: {e}", file=sys.stderr)¶
sys.exit(1)¶

prompt = input_data.get("prompt", "")¶

# Check for sensitive patterns¶
sensitive_patterns = [¶
(r"(?i)\b(password|secret|key|token)\s*[:=]", "Prompt contains potential secrets"),¶
]¶

for pattern, message in sensitive_patterns:¶
if re.search(pattern, prompt):¶
# Use JSON output to block with a specific reason¶
output = {¶
"decision": "block",¶
"reason": f"Security policy violation: {message}. Please rephrase your request without sensitive information."¶
}¶
print(json.dumps(output))¶
sys.exit(0)¶

# Add current time to context¶
context = f"Current time: {datetime.datetime.now()}"¶
print(context)¶

"""¶
The following is also equivalent:¶
print(json.dumps({¶
"hookSpecificOutput": {¶
"hookEventName": "UserPromptSubmit",¶
"additionalContext": context,¶
},¶
}))¶
"""¶

# Allow the prompt to proceed with the additional context¶
sys.exit(0)¶
```¶

#### JSON Output Example: PreToolUse with Approval¶

```python theme={null}¶
#!/usr/bin/env python3¶
import json¶
import sys¶

# Load input from stdin¶
try:¶
input_data = json.load(sys.stdin)¶
except json.JSONDecodeError as e:¶
print(f"Error: Invalid JSON input: {e}", file=sys.stderr)¶
sys.exit(1)¶

tool_name = input_data.get("tool_name", "")¶
tool_input = input_data.get("tool_input", {})¶

# Example: Auto-approve file reads for documentation files¶
if tool_name == "Read":¶
file_path = tool_input.get("file_path", "")¶
if file_path.endswith((".md", ".mdx", ".txt", ".json")):¶
# Use JSON output to auto-approve the tool call¶
output = {¶
"decision": "approve",¶
"reason": "Documentation file auto-approved",¶
"suppressOutput": True # Don't show in verbose mode¶
}¶
print(json.dumps(output))¶
sys.exit(0)¶

# For other cases, let the normal permission flow proceed¶
sys.exit(0)¶
```¶

## Working with MCP Tools¶

Claude Code hooks work seamlessly with¶
[Model Context Protocol (MCP) tools](/en/mcp). When MCP servers¶
provide tools, they appear with a special naming pattern that you can match in¶
your hooks.¶

### MCP Tool Naming¶

MCP tools follow the pattern `mcp__<server>__<tool>`, for example:¶

* `mcp__memory__create_entities` - Memory server's create entities tool¶
* `mcp__filesystem__read_file` - Filesystem server's read file tool¶
* `mcp__github__search_repositories` - GitHub server's search tool¶

### Configuring Hooks for MCP Tools¶

You can target specific MCP tools or entire MCP servers:¶

```json theme={null}¶
{¶
"hooks": {¶
"PreToolUse": [¶
{¶
"matcher": "mcp__memory__.*",¶
"hooks": [¶
{¶
"type": "command",¶
"command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"¶
}¶
]¶
},¶
{¶
"matcher": "mcp__.*__write.*",¶
"hooks": [¶
{¶
"type": "command",¶
"command": "/home/user/scripts/validate-mcp-write.py"¶
}¶
]¶
}¶
]¶
}¶
}¶
```¶

## Examples¶

<Tip>¶
For practical examples including code formatting, notifications, and file protection, see [More Examples](/en/hooks-guide#more-examples) in the get started guide.¶
</Tip>¶

## Security Considerations¶

### Disclaimer¶

**USE AT YOUR OWN RISK**: Claude Code hooks execute arbitrary shell commands on¶
your system automatically. By using hooks, you acknowledge that:¶

* You are solely responsible for the commands you configure¶
* Hooks can modify, delete, or access any files your user account can access¶
* Malicious or poorly written hooks can cause data loss or system damage¶
* Anthropic provides no warranty and assumes no liability for any damages¶
resulting from hook usage¶
* You should thoroughly test hooks in a safe environment before production use¶

Always review and understand any hook commands before adding them to your¶
configuration.¶

### Security Best Practices¶

Here are some key practices for writing more secure hooks:¶

1. **Validate and sanitize inputs** - Never trust input data blindly¶
2. **Always quote shell variables** - Use `"$VAR"` not `$VAR`¶
3. **Block path traversal** - Check for `..` in file paths¶
4. **Use absolute paths** - Specify full paths for scripts (use¶
"\$CLAUDE\_PROJECT\_DIR" for the project path)¶
5. **Skip sensitive files** - Avoid `.env`, `.git/`, keys, etc.¶

### Configuration Safety¶

Direct edits to hooks in settings files don't take effect immediately. Claude¶
Code:¶

1. Captures a snapshot of hooks at startup¶
2. Uses this snapshot throughout the session¶
3. Warns if hooks are modified externally¶
4. Requires review in `/hooks` menu for changes to apply¶

This prevents malicious hook modifications from affecting your current session.¶

## Hook Execution Details¶

* **Timeout**: 60-second execution limit by default, configurable per command.¶
* A timeout for an individual command does not affect the other commands.¶
* **Parallelization**: All matching hooks run in parallel¶
* **Deduplication**: Multiple identical hook commands are deduplicated automatically¶
* **Environment**: Runs in current directory with Claude Code's environment¶
* The `CLAUDE_PROJECT_DIR` environment variable is available and contains the¶
absolute path to the project root directory (where Claude Code was started)¶
* 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.¶
* **Input**: JSON via stdin¶
* **Output**:¶
* PreToolUse/PermissionRequest/PostToolUse/Stop/SubagentStop: Progress shown in verbose mode (ctrl+o)¶
* Notification/SessionEnd: Logged to debug only (`--debug`)¶
* UserPromptSubmit/SessionStart/Setup: stdout added as context for Claude¶

## Debugging¶

### Basic Troubleshooting¶

If your hooks aren't working:¶

1. **Check configuration** - Run `/hooks` to see if your hook is registered¶
2. **Verify syntax** - Ensure your JSON settings are valid¶
3. **Test commands** - Run hook commands manually first¶
4. **Check permissions** - Make sure scripts are executable¶
5. **Review logs** - Use `claude --debug` to see hook execution details¶

Common issues:¶

* **Quotes not escaped** - Use `\"` inside JSON strings¶
* **Wrong matcher** - Check tool names match exactly (case-sensitive)¶
* **Command not found** - Use full paths for scripts¶

### Advanced Debugging¶

For complex hook issues:¶

1. **Inspect hook execution** - Use `claude --debug` to see detailed hook¶
execution¶
2. **Validate JSON schemas** - Test hook input/output with external tools¶
3. **Check environment variables** - Verify Claude Code's environment is correct¶
4. **Test edge cases** - Try hooks with unusual file paths or inputs¶
5. **Monitor system resources** - Check for resource exhaustion during hook¶
execution¶
6. **Use structured logging** - Implement logging in your hook scripts¶

### Debug Output Example¶

Use `claude --debug` to see hook execution details:¶

```¶
[DEBUG] Executing hooks for PostToolUse:Write¶
[DEBUG] Getting matching hook commands for PostToolUse with query: Write¶
[DEBUG] Found 1 hook matchers in settings¶
[DEBUG] Matched 1 hooks for query "Write"¶
[DEBUG] Found 1 hook commands to execute¶
[DEBUG] Executing hook command: <Your command> with timeout 60000ms¶
[DEBUG] Hook command completed with status 0: <Your stdout>¶
```¶

Progress messages appear in verbose mode (ctrl+o) showing:¶

* Which hook is running¶
* Command being executed¶
* Success/failure status¶
* Output or error messages
The 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.¶

| Event | When it fires |¶
| :------------------- | :--------------------------------------------------- |¶
| `SessionStart` | When a session begins or resumes |¶
| `UserPromptSubmit` | When you submit a prompt, before Claude processes it |¶
| `PreToolUse` | Before a tool call executes. Can block it |¶
| `PermissionRequest` | When a permission dialog appears |¶
| `PostToolUse` | After a tool call succeeds |¶
| `PostToolUseFailure` | After a tool call fails |¶
| `Notification` | When Claude Code sends a notification |¶
| `SubagentStart` | When a subagent is spawned |¶
| `SubagentStop` | When a subagent finishes |¶
| `Stop` | When Claude finishes responding |¶
| `PreCompact` | Before context compaction |¶
| `SessionEnd` | When a session terminates |¶

### How a hook resolves¶

To 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:¶

```json theme={null}¶
{¶
"hooks": {¶
"PreToolUse": [¶
{¶
"matcher": "Bash",¶
"hooks": [¶
{¶
"type": "command",¶
"command": ".claude/hooks/block-rm.sh"¶
}¶
]¶
}¶
]¶
}¶
}¶
```¶

The script reads the JSON input from stdin, extracts the command, and blocks it if it contains `rm -rf`:¶

```bash theme={null}¶
#!/bin/bash¶
# .claude/hooks/block-rm.sh¶
COMMAND=$(jq -r '.tool_input.command')¶

if echo "$COMMAND" | grep -q 'rm -rf'; then¶
echo '{"decision":"block","reason":"Destructive command blocked by hook"}'¶
else¶
exit 0 # allow the command¶
fi¶
```¶

Now suppose Claude Code decides to run `Bash "rm -rf /tmp/build"`. Here's what happens:¶

<Frame>¶
<img src="https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=7c13f51ffcbc37d22a593b27e2f2de72" alt="Hook resolution flow: PreToolUse event fires, matcher checks for Bash match, hook handler runs, result returns to Claude Code" data-og-width="780" width="780" data-og-height="290" height="290" data-path="images/hook-resolution.svg" data-optimize="true" data-opv="3" srcset="https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=280&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=36a39a07e8bc1995dcb4639e09846905 280w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=560&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=6568d90c596c7605bbac2c325b0a0c86 560w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=840&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=255a6f68b9475a0e41dbde7b88002dad 840w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=1100&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=dcecf8d5edc88cd2bc49deb006d5760d 1100w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=1650&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=04fe51bf69ae375e9fd517f18674e35f 1650w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=2500&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=b1b76e0b77fddb5c7fa7bf302dacd80b 2500w" />¶
</Frame>¶

<Steps>¶
<Step title="Event fires">¶
The `PreToolUse` event fires. Claude Code sends the tool input as JSON on stdin to the hook:¶

```json theme={null}¶
{ "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... }¶
```¶
</Step>¶

<Step title="Matcher checks">¶
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.¶
</Step>¶

<Step title="Hook handler runs">¶
The script extracts `"rm -rf /tmp/build"` from the input and finds `rm -rf`, so it prints a decision to stdout:¶

```json theme={null}¶
{ "decision": "block", "reason": "Destructive command blocked by hook" }¶
```¶

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.¶
</Step>¶

<Step title="Claude Code acts on the result">¶
Claude Code reads the JSON decision, blocks the tool call, and shows Claude the reason.¶
</Step>¶
</Steps>¶

The [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.¶

## Configuration¶

Hooks are defined in JSON settings files. The configuration has three levels of nesting:¶

1. Choose a [hook event](#hook-events) to respond to, like `PreToolUse` or `Stop`¶
2. Add a [matcher group](#matcher-patterns) to filter when it fires, like "only for the Bash tool"¶
3. Define one or more [hook handlers](#hook-handler-fields) to run when matched¶

See [How a hook resolves](#how-a-hook-resolves) above for a complete walkthrough with an annotated example.¶

<Note>¶
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.¶
</Note>¶

### Hook locations¶

Where you define a hook determines its scope:¶

| Location | Scope | Shareable |¶
| :--------------------------------------------------------- | :---------------------------- | :--------------------------------- |¶
| `~/.claude/settings.json` | All your projects | No, local to your machine |¶
| `.claude/settings.json` | Single project | Yes, can be committed to the repo |¶
| `.claude/settings.local.json` | Single project | No, gitignored |¶
| Managed policy settings | Organization-wide | Yes, admin-controlled |¶
| [Plugin](/en/plugins) `hooks/hooks.json` | When plugin is enabled | Yes, bundled with the plugin |¶
| [Skill](/en/skills) or [agent](/en/sub-agents) frontmatter | While the component is active | Yes, defined in the component file |¶

For 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).¶

### Matcher patterns¶

The `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:¶

| Event | What the matcher filters | Example matcher values |¶
| :--------------------------------------------------------------------- | :------------------------ | :----------------------------------------------------------------------------- |¶
| `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest` | tool name | `Bash`, `Edit\|Write`, `mcp__.*` |¶
| `SessionStart` | how the session started | `startup`, `resume`, `clear`, `compact` |¶
| `SessionEnd` | why the session ended | `clear`, `logout`, `prompt_input_exit`, `bypass_permissions_disabled`, `other` |¶
| `Notification` | notification type | `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog` |¶
| `SubagentStart` | agent type | `Bash`, `Explore`, `Plan`, or custom agent names |¶
| `PreCompact` | what triggered compaction | `manual`, `auto` |¶
| `SubagentStop` | agent type | same values as `SubagentStart` |¶
| `UserPromptSubmit`, `Stop` | no matcher support | always fires on every occurrence |¶

The 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.¶

This example runs a linting script only when Claude writes or edits a file:¶

```json theme={null}¶
{¶
"hooks": {¶
"PostToolUse": [¶
{¶
"matcher": "Edit|Write",¶
"hooks": [¶
{¶
"type": "command",¶
"command": "/path/to/lint-check.sh"¶
}¶
]¶
}¶
]¶
}¶
}¶
```¶

`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.¶

#### Match MCP tools¶

[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.¶

MCP tools follow the naming pattern `mcp__<server>__<tool>`, for example:¶

* `mcp__memory__create_entities`: Memory server's create entities tool¶
* `mcp__filesystem__read_file`: Filesystem server's read file tool¶
* `mcp__github__search_repositories`: GitHub server's search tool¶

Use regex patterns to target specific MCP tools or groups of tools:¶

* `mcp__memory__.*` matches all tools from the `memory` server¶
* `mcp__.*__write.*` matches any tool containing "write" from any server¶

This example logs all memory server operations and validates write operations from any MCP server:¶

```json theme={null}¶
{¶
"hooks": {¶
"PreToolUse": [¶
{¶
"matcher": "mcp__memory__.*",¶
"hooks": [¶
{¶
"type": "command",¶
"command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"¶
}¶
]¶
},¶
{¶
"matcher": "mcp__.*__write.*",¶
"hooks": [¶
{¶
"type": "command",¶
"command": "/home/user/scripts/validate-mcp-write.py"¶
}¶
]¶
}¶
]¶
}¶
}¶
```¶

### Hook handler fields¶

Each 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:¶

* **[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.¶
* **[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).¶
* **[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).¶

#### Common fields¶

These fields apply to all hook types:¶

| Field | Required | Description |¶
| :-------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------- |¶
| `type` | yes | `"command"`, `"prompt"`, or `"agent"` |¶
| `timeout` | no | Seconds before canceling. Defaults: 600 for command, 30 for prompt, 60 for agent |¶
| `statusMessage` | no | Custom spinner message displayed while the hook runs |¶
| `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) |¶

#### Command hook fields¶

In addition to the [common fields](#common-fields), command hooks accept these fields:¶

| Field | Required | Description |¶
| :-------- | :------- | :------------------------------------------------------------------------------------------------------------------ |¶
| `command` | yes | Shell command to execute |¶
| `async` | no | If `true`, runs in the background without blocking. See [Run hooks in the background](#run-hooks-in-the-background) |¶

#### Prompt and agent hook fields¶

In addition to the [common fields](#common-fields), prompt and agent hooks accept these fields:¶

| Field | Required | Description |¶
| :------- | :------- | :------------------------------------------------------------------------------------------ |¶
| `prompt` | yes | Prompt text to send to the model. Use `$ARGUMENTS` as a placeholder for the hook input JSON |¶
| `model` | no | Model to use for evaluation. Defaults to a fast model |¶

All 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.¶

### Reference scripts by path¶

Use environment variables to reference hook scripts relative to the project or plugin root, regardless of the working directory when the hook runs:¶

* `$CLAUDE_PROJECT_DIR`: the project root. Wrap in quotes to handle paths with spaces.¶
* `${CLAUDE_PLUGIN_ROOT}`: the plugin's root directory, for scripts bundled with a [plugin](/en/plugins).¶

<Tabs>¶
<Tab title="Project scripts">¶
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:¶

```json theme={null}¶
{¶
"hooks": {¶
"PostToolUse": [¶
{¶
"matcher": "Write|Edit",¶
"hooks": [¶
{¶
"type": "command",¶
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-style.sh"¶
}¶
]¶
}¶
]¶
}¶
}¶
```¶
</Tab>¶

<Tab title="Plugin scripts">¶
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.¶

This example runs a formatting script bundled with the plugin:¶

```json theme={null}¶
{¶
"description": "Automatic code formatting",¶
"hooks": {¶
"PostToolUse": [¶
{¶
"matcher": "Write|Edit",¶
"hooks": [¶
{¶
"type": "command",¶
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",¶
"timeout": 30¶
}¶
]¶
}¶
]¶
}¶
}¶
```¶

See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.¶
</Tab>¶
</Tabs>¶

### Hooks in skills and agents¶

In 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.¶

All hook events are supported. For subagents, `Stop` hooks are automatically converted to `SubagentStop` since that is the event that fires when a subagent completes.¶

Hooks use the same configuration format as settings-based hooks but are scoped to the component's lifetime and cleaned up when it finishes.¶

This skill defines a `PreToolUse` hook that runs a security validation script before each `Bash` command:¶

```yaml theme={null}¶
---¶
name: secure-operations¶
description: Perform operations with security checks¶
hooks:¶
PreToolUse:¶
- matcher: "Bash"¶
hooks:¶
- type: command¶
command: "./scripts/security-check.sh"¶
---¶
```¶

Agents use the same format in their YAML frontmatter.¶

### The `/hooks` menu¶

Type `/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.¶

Each hook in the menu is labeled with a bracket prefix indicating its source:¶

* `[User]`: from `~/.claude/settings.json`¶
* `[Project]`: from `.claude/settings.json`¶
* `[Local]`: from `.claude/settings.local.json`¶
* `[Plugin]`: from a plugin's `hooks/hooks.json`, read-only¶

### Disable or remove hooks¶

To remove a hook, delete its entry from the settings JSON file, or use the `/hooks` menu and select the hook to delete it.¶

To 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.¶

Direct 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.¶

## Hook input and output¶

Hooks 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.¶

### Common input fields¶

All hook events receive these fields via stdin as JSON, in addition to event-specific fields documented in each [hook event](#hook-events) section:¶

| Field | Description |¶
| :---------------- | :--------------------------------------------------------------------------------------------------------------------------------- |¶
| `session_id` | Current session identifier |¶
| `transcript_path` | Path to conversation JSON |¶
| `cwd` | Current working directory when the hook is invoked |¶
| `permission_mode` | Current [permission mode](/en/iam#permission-modes): `"default"`, `"plan"`, `"acceptEdits"`, `"dontAsk"`, or `"bypassPermissions"` |¶
| `hook_event_name` | Name of the event that fired |¶

For example, a `PreToolUse` hook for a Bash command receives this on stdin:¶

```json theme={null}¶
{¶
"session_id": "abc123",¶
"transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",¶
"cwd": "/home/user/my-project",¶
"permission_mode": "default",¶
"hook_event_name": "PreToolUse",¶
"tool_name": "Bash",¶
"tool_input": {¶
"command": "npm test"¶
}¶
}¶
```¶

The `tool_name` and `tool_input` fields are event-specific. Each [hook event](#hook-events) section documents the additional fields for that event.¶

### Exit code output¶

The exit code from your hook command tells Claude Code whether the action should proceed, be blocked, or be ignored.¶

**Exit 0** means success. Claude Code parses stdout for [JSON output fields](#json-output) like `decision` or `reason`. 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.¶

**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.¶

**Any other exit code** is a non-blocking error. stderr is shown in verbose mode (`Ctrl+O`) and execution continues.¶

For example, a hook command script that blocks dangerous Bash commands:¶

```bash theme={null}¶
#!/bin/bash¶
# Reads JSON input from stdin, checks the command¶
command=$(jq -r '.tool_input.command' < /dev/stdin)¶

if [[ "$command" == rm* ]]; then¶
echo "Blocked: rm commands are not allowed" >&2¶
exit 2 # Blocking error: tool call is prevented¶
fi¶

exit 0 # Success: tool call proceeds¶
```¶

#### Exit code 2 behavior per event¶

Exit 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.¶

| Hook event | Can block? | What happens on exit 2 |¶
| :------------------- | :--------- | :-------------------------------------------------------- |¶
| `PreToolUse` | Yes | Blocks the tool call |¶
| `PermissionRequest` | Yes | Denies the permission |¶
| `UserPromptSubmit` | Yes | Blocks prompt processing and erases the prompt |¶
| `Stop` | Yes | Prevents Claude from stopping, continues the conversation |¶
| `SubagentStop` | Yes | Prevents the subagent from stopping |¶
| `PostToolUse` | No | Shows stderr to Claude (tool already ran) |¶
| `PostToolUseFailure` | No | Shows stderr to Claude (tool already failed) |¶
| `Notification` | No | Shows stderr to user only |¶
| `SubagentStart` | No | Shows stderr to user only |¶
| `SessionStart` | No | Shows stderr to user only |¶
| `SessionEnd` | No | Shows stderr to user only |¶
| `PreCompact` | No | Shows stderr to user only |¶

### JSON output¶

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.¶

Instead of relying on exit codes alone, hooks can print JSON to stdout on exit 0. Claude Code reads specific fields from this JSON to decide what to do next.¶

Your 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.¶

The JSON object has two parts:¶

* **Top-level fields** like `continue` and `decision` work across all events. These are listed in the table below.¶
* **`hookSpecificOutput`** is a nested object for event-specific fields like `permissionDecision` or `additionalContext`. It requires a `hookEventName` field set to the event name, like `"PreToolUse"` or `"Stop"`. Each event's decision control section under [Hook events](#hook-events) documents what fields go here.¶

| Field | Default | Description |¶
| :--------------- | :------ | :---------------------------------------------------------------------------------------------------------------------------------------------------- |¶
| `continue` | `true` | If `false`, Claude stops processing entirely after the hook runs. Takes precedence over event-specific fields like `decision` or `permissionDecision` |¶
| `stopReason` | none | Message shown to the user when `continue` is `false`. Not shown to Claude |¶
| `suppressOutput` | `false` | If `true`, hides stdout from verbose mode output |¶
| `systemMessage` | none | Warning message shown to the user |¶

This example uses a top-level field to stop Claude:¶

```json theme={null}¶
{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }¶
```¶

This example uses `hookSpecificOutput` to deny a PreToolUse tool call:¶

```json theme={null}¶
{¶
"hookSpecificOutput": {¶
"hookEventName": "PreToolUse",¶
"permissionDecision": "deny",¶
"permissionDecisionReason": "Database writes are not allowed"¶
}¶
}¶
```¶

For 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).¶

## Hook events¶

Each 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.¶

### SessionStart¶

Runs 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.¶

SessionStart runs on every session, so keep these hooks fast.¶

The matcher value corresponds to how the session was initiated:¶

| Matcher | When it fires |¶
| :-------- | :------------------------------------- |¶
| `startup` | New session |¶
| `resume` | `--resume`, `--continue`, or `/resume` |¶
| `clear` | `/clear` |¶
| `compact` | Auto or manual compaction |¶

#### SessionStart input¶

In 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.¶

```json theme={null}¶
{¶
"session_id": "abc123",¶
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",¶
"cwd": "/Users/...",¶
"permission_mode": "default",¶
"hook_event_name": "SessionStart",¶
"source": "startup",¶
"model": "claude-sonnet-4-5-20250929"¶
}¶
```¶

#### SessionStart decision control¶

Any 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:¶

| Field | Description |¶
| :------------------ | :------------------------------------------------------------------------ |¶
| `additionalContext` | String added to Claude's context. Multiple hooks' values are concatenated |¶

```json theme={null}¶
{¶
"hookSpecificOutput": {¶
"hookEventName": "SessionStart",¶
"additionalContext": "My additional context here"¶
}¶
}¶
```¶

#### Persist environment variables¶

SessionStart 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.¶

To set individual environment variables, write `export` statements to `CLAUDE_ENV_FILE`. Use append (`>>`) to preserve variables set by other hooks:¶

```bash theme={null}¶
#!/bin/bash¶

if [ -n "$CLAUDE_ENV_FILE" ]; then¶
echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"¶
echo 'export DEBUG_LOG=true' >> "$CLAUDE_ENV_FILE"¶
echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"¶
fi¶

exit 0¶
```¶

To capture all environment changes from setup commands, compare the exported variables before and after:¶

```bash theme={null}¶
#!/bin/bash¶

ENV_BEFORE=$(export -p | sort)¶

# Run your setup commands that modify the environment¶
source ~/.nvm/nvm.sh¶
nvm use 20¶

if [ -n "$CLAUDE_ENV_FILE" ]; then¶
ENV_AFTER=$(export -p | sort)¶
comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"¶
fi¶

exit 0¶
```¶

Any variables written to this file will be available in all subsequent Bash commands that Claude Code executes during the session.¶

<Note>¶
`CLAUDE_ENV_FILE` is available for SessionStart hooks. Other hook types do not have access to this variable.¶
</Note>¶

### UserPromptSubmit¶

Runs when the user submits a prompt, before Claude processes it. This allows you¶
to add additional context based on the prompt/conversation, validate prompts, or¶
block certain types of prompts.¶

#### UserPromptSubmit input¶

In addition to the [common input fields](#common-input-fields), UserPromptSubmit hooks receive the `prompt` field containing the text the user submitted.¶

```json theme={null}¶
{¶
"session_id": "abc123",¶
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",¶
"cwd": "/Users/...",¶
"permission_mode": "default",¶
"hook_event_name": "UserPromptSubmit",¶
"prompt": "Write a function to calculate the factorial of a number"¶
}¶
```¶

#### UserPromptSubmit decision control¶

`UserPromptSubmit` hooks can control whether a user prompt is processed and add context. All [JSON output fields](#json-output) are available.¶

There are two ways to add context to the conversation on exit code 0:¶

* **Plain text stdout**: any non-JSON text written to stdout is added as context¶
* **JSON with `additionalContext`**: use the JSON format below for more control. The `additionalContext` field is added as context¶

Plain stdout is shown as hook output in the transcript. The `additionalContext` field is added more discretely.¶

To block a prompt, return a JSON object with `decision` set to `"block"`:¶

| Field | Description |¶
| :------------------ | :----------------------------------------------------------------------------------------------------------------- |¶
| `decision` | `"block"` prevents the prompt from being processed and erases it from context. Omit to allow the prompt to proceed |¶
| `reason` | Shown to the user when `decision` is `"block"`. Not added to context |¶
| `additionalContext` | String added to Claude's context |¶

```json theme={null}¶
{¶
"decision": "block",¶
"reason": "Explanation for decision",¶
"hookSpecificOutput": {¶
"hookEventName": "UserPromptSubmit",¶
"additionalContext": "My additional context here"¶
}¶
}¶
```¶

<Note>¶
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¶
block prompts or want more structured control.¶
</Note>¶

### PreToolUse¶

Runs 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).¶

Use [PreToolUse decision control](#pretooluse-decision-control) to allow, deny, or ask for permission to use the tool.¶

#### PreToolUse input¶

In 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:¶

##### Bash¶

Executes shell commands.¶

| Field | Type | Example | Description |¶
| :------------------ | :------ | :----------------- | :-------------------------------------------- |¶
| `command` | string | `"npm test"` | The shell command to execute |¶
| `description` | string | `"Run test suite"` | Optional description of what the command does |¶
| `timeout` | number | `120000` | Optional timeout in milliseconds |¶
| `run_in_background` | boolean | `false` | Whether to run the command in background |¶

##### Write¶

Creates or overwrites a file.¶

| Field | Type | Example | Description |¶
| :---------- | :----- | :-------------------- | :--------------------------------- |¶
| `file_path` | string | `"/path/to/file.txt"` | Absolute path to the file to write |¶
| `content` | string | `"file content"` | Content to write to the file |¶

##### Edit¶

Replaces a string in an existing file.¶

| Field | Type | Example | Description |¶
| :------------ | :------ | :-------------------- | :--------------------------------- |¶
| `file_path` | string | `"/path/to/file.txt"` | Absolute path to the file to edit |¶
| `old_string` | string | `"original text"` | Text to find and replace |¶
| `new_string` | string | `"replacement text"` | Replacement text |¶
| `replace_all` | boolean | `false` | Whether to replace all occurrences |¶

##### Read¶

Reads file contents.¶

| Field | Type | Example | Description |¶
| :---------- | :----- | :-------------------- | :----------------------------------------- |¶
| `file_path` | string | `"/path/to/file.txt"` | Absolute path to the file to read |¶
| `offset` | number | `10` | Optional line number to start reading from |¶
| `limit` | number | `50` | Optional number of lines to read |¶

##### Glob¶

Finds files matching a glob pattern.¶

| Field | Type | Example | Description |¶
| :-------- | :----- | :--------------- | :--------------------------------------------------------------------- |¶
| `pattern` | string | `"**/*.ts"` | Glob pattern to match files against |¶
| `path` | string | `"/path/to/dir"` | Optional directory to search in. Defaults to current working directory |¶

##### Grep¶

Searches file contents with regular expressions.¶

| Field | Type | Example | Description |¶
| :------------ | :------ | :--------------- | :------------------------------------------------------------------------------------ |¶
| `pattern` | string | `"TODO.*fix"` | Regular expression pattern to search for |¶
| `path` | string | `"/path/to/dir"` | Optional file or directory to search in |¶
| `glob` | string | `"*.ts"` | Optional glob pattern to filter files |¶
| `output_mode` | string | `"content"` | `"content"`, `"files_with_matches"`, or `"count"`. Defaults to `"files_with_matches"` |¶
| `-i` | boolean | `true` | Case insensitive search |¶
| `multiline` | boolean | `false` | Enable multiline matching |¶

##### WebFetch¶

Fetches and processes web content.¶

| Field | Type | Example | Description |¶
| :------- | :----- | :---------------------------- | :----------------------------------- |¶
| `url` | string | `"https://example.com/api"` | URL to fetch content from |¶
| `prompt` | string | `"Extract the API endpoints"` | Prompt to run on the fetched content |¶

##### WebSearch¶

Searches the web.¶

| Field | Type | Example | Description |¶
| :---------------- | :----- | :----------------------------- | :------------------------------------------------ |¶
| `query` | string | `"react hooks best practices"` | Search query |¶
| `allowed_domains` | array | `["docs.example.com"]` | Optional: only include results from these domains |¶
| `blocked_domains` | array | `["spam.example.com"]` | Optional: exclude results from these domains |¶

##### Task¶

Spawns a [subagent](/en/sub-agents).¶

| Field | Type | Example | Description |¶
| :-------------- | :----- | :------------------------- | :------------------------------------------- |¶
| `prompt` | string | `"Find all API endpoints"` | The task for the agent to perform |¶
| `description` | string | `"Find API endpoints"` | Short description of the task |¶
| `subagent_type` | string | `"Explore"` | Type of specialized agent to use |¶
| `model` | string | `"sonnet"` | Optional model alias to override the default |¶

#### PreToolUse decision control¶

`PreToolUse` hooks can control whether a tool call proceeds. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return a `hookSpecificOutput` object with these event-specific fields:¶

| Field | Description |¶
| :------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |¶
| `permissionDecision` | `"allow"` bypasses the permission system, `"deny"` prevents the tool call, `"ask"` prompts the user to confirm |¶
| `permissionDecisionReason` | For `"allow"` and `"ask"`, shown to the user but not Claude. For `"deny"`, shown to Claude |¶
| `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 |¶
| `additionalContext` | String added to Claude's context before the tool executes |¶

```json theme={null}¶
{¶
"hookSpecificOutput": {¶
"hookEventName": "PreToolUse",¶
"permissionDecision": "allow",¶
"permissionDecisionReason": "My reason here",¶
"updatedInput": {¶
"field_to_modify": "new value"¶
},¶
"additionalContext": "Current environment: production. Proceed with caution."¶
}¶
}¶
```¶

<Note>¶
The `decision` and `reason` fields are deprecated for PreToolUse hooks.¶
Use `hookSpecificOutput.permissionDecision` and¶
`hookSpecificOutput.permissionDecisionReason` instead. The deprecated fields¶
`"approve"` and `"block"` map to `"allow"` and `"deny"` respectively.¶
</Note>¶

### PermissionRequest¶

Runs when the user is shown a permission dialog.¶
Use [PermissionRequest decision control](#permissionrequest-decision-control) to allow or deny on behalf of the user.¶

Matches on tool name, same values as PreToolUse.¶

#### PermissionRequest input¶

PermissionRequest 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.¶

```json theme={null}¶
{¶
"session_id": "abc123",¶
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",¶
"cwd": "/Users/...",¶
"permission_mode": "default",¶
"hook_event_name": "PermissionRequest",¶
"tool_name": "Bash",¶
"tool_input": {¶
"command": "rm -rf node_modules",¶
"description": "Remove node_modules directory"¶
},¶
"permission_suggestions": [¶
{ "type": "toolAlwaysAllow", "tool": "Bash" }¶
]¶
}¶
```¶

#### PermissionRequest decision control¶

`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:¶

| Field | Description |¶
| :------------------- | :------------------------------------------------------------------------------------------------------------- |¶
| `behavior` | `"allow"` grants the permission, `"deny"` denies it |¶
| `updatedInput` | For `"allow"` only: modifies the tool's input parameters before execution |¶
| `updatedPermissions` | For `"allow"` only: applies permission rule updates, equivalent to the user selecting an "always allow" option |¶
| `message` | For `"deny"` only: tells Claude why the permission was denied |¶
| `interrupt` | For `"deny"` only: if `true`, stops Claude |¶

```json theme={null}¶
{¶
"hookSpecificOutput": {¶
"hookEventName": "PermissionRequest",¶
"decision": {¶
"behavior": "allow",¶
"updatedInput": {¶
"command": "npm run lint"¶
}¶
}¶
}¶
}¶
```¶

### PostToolUse¶

Runs immediately after a tool completes successfully.¶

Matches on tool name, same values as PreToolUse.¶

#### PostToolUse input¶

`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.¶

```json theme={null}¶
{¶
"session_id": "abc123",¶
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",¶
"cwd": "/Users/...",¶
"permission_mode": "default",¶
"hook_event_name": "PostToolUse",¶
"tool_name": "Write",¶
"tool_input": {¶
"file_path": "/path/to/file.txt",¶
"content": "file content"¶
},¶
"tool_response": {¶
"filePath": "/path/to/file.txt",¶
"success": true¶
},¶
"tool_use_id": "toolu_01ABC123..."¶
}¶
```¶

#### PostToolUse decision control¶

`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:¶

| Field | Description |¶
| :--------------------- | :----------------------------------------------------------------------------------------- |¶
| `decision` | `"block"` prompts Claude with the `reason`. Omit to allow the action to proceed |¶
| `reason` | Explanation shown to Claude when `decision` is `"block"` |¶
| `additionalContext` | Additional context for Claude to consider |¶
| `updatedMCPToolOutput` | For [MCP tools](#match-mcp-tools) only: replaces the tool's output with the provided value |¶

```json theme={null}¶
{¶
"decision": "block",¶
"reason": "Explanation for decision",¶
"hookSpecificOutput": {¶
"hookEventName": "PostToolUse",¶
"additionalContext": "Additional information for Claude"¶
}¶
}¶
```¶

### PostToolUseFailure¶

Runs 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.¶

Matches on tool name, same values as PreToolUse.¶

#### PostToolUseFailure input¶

PostToolUseFailure hooks receive the same `tool_name` and `tool_input` fields as PostToolUse, along with error information as top-level fields:¶

```json theme={null}¶
{¶
"session_id": "abc123",¶
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",¶
"cwd": "/Users/...",¶
"permission_mode": "default",¶
"hook_event_name": "PostToolUseFailure",¶
"tool_name": "Bash",¶
"tool_input": {¶
"command": "npm test",¶
"description": "Run test suite"¶
},¶
"tool_use_id": "toolu_01ABC123...",¶
"error": "Command exited with non-zero status code 1",¶
"is_interrupt": false¶
}¶
```¶

| Field | Description |¶
| :------------- | :------------------------------------------------------------------------------ |¶
| `error` | String describing what went wrong |¶
| `is_interrupt` | Optional boolean indicating whether the failure was caused by user interruption |¶

#### PostToolUseFailure decision control¶

`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:¶

| Field | Description |¶
| :------------------ | :------------------------------------------------------------ |¶
| `additionalContext` | Additional context for Claude to consider alongside the error |¶

```json theme={null}¶
{¶
"hookSpecificOutput": {¶
"hookEventName": "PostToolUseFailure",¶
"additionalContext": "Additional information about the failure for Claude"¶
}¶
}¶
```¶

### Notification¶

Runs 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.¶

Use 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:¶

```json theme={null}¶
{¶
"hooks": {¶
"Notification": [¶
{¶
"matcher": "permission_prompt",¶
"hooks": [¶
{¶
"type": "command",¶
"command": "/path/to/permission-alert.sh"¶
}¶
]¶
},¶
{¶
"matcher": "idle_prompt",¶
"hooks": [¶
{¶
"type": "command",¶
"command": "/path/to/idle-notification.sh"¶
}¶
]¶
}¶
]¶
}¶
}¶
```¶

#### Notification input¶

In 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.¶

```json theme={null}¶
{¶
"session_id": "abc123",¶
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",¶
"cwd": "/Users/...",¶
"permission_mode": "default",¶
"hook_event_name": "Notification",¶
"message": "Claude needs your permission to use Bash",¶
"title": "Permission needed",¶
"notification_type": "permission_prompt"¶
}¶
```¶

Notification 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:¶

| Field | Description |¶
| :------------------ | :------------------------------- |¶
| `additionalContext` | String added to Claude's context |¶

### SubagentStart¶

Runs 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/`).¶

#### SubagentStart input¶

In 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).¶

```json theme={null}¶
{¶
"session_id": "abc123",¶
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",¶
"cwd": "/Users/...",¶
"permission_mode": "default",¶
"hook_event_name": "SubagentStart",¶
"agent_id": "agent-abc123",¶
"agent_type": "Explore"¶
}¶
```¶

SubagentStart 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:¶

| Field | Description |¶
| :------------------ | :------------------------------------- |¶
| `additionalContext` | String added to the subagent's context |¶

```json theme={null}¶
{¶
"hookSpecificOutput": {¶
"hookEventName": "SubagentStart",¶
"additionalContext": "Follow security guidelines for this task"¶
}¶
}¶
```¶

### SubagentStop¶

Runs when a Claude Code subagent has finished responding. Matches on agent type, same values as SubagentStart.¶

#### SubagentStop input¶

In addition to the [common input fields](#common-input-fields), SubagentStop hooks receive `stop_hook_active`, `agent_id`, `agent_type`, and `agent_transcript_path`. The `agent_type` field is the value used for matcher filtering. The `transcript_path` is the main session's transcript, while `agent_transcript_path` is the subagent's own transcript stored in a nested `subagents/` folder.¶

```json theme={null}¶
{¶
"session_id": "abc123",¶
"transcript_path": "~/.claude/projects/.../abc123.jsonl",¶
"cwd": "/Users/...",¶
"permission_mode": "default",¶
"hook_event_name": "SubagentStop",¶
"stop_hook_active": false,¶
"agent_id": "def456",¶
"agent_type": "Explore",¶
"agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl"¶
}¶
```¶

SubagentStop hooks use the same decision control format as [Stop hooks](#stop-decision-control).¶

### Stop¶

Runs when the main Claude Code agent has finished responding. Does not run if¶
the stoppage occurred due to a user interrupt.¶

#### Stop input¶

In addition to the [common input fields](#common-input-fields), Stop hooks receive `stop_hook_active`. This field is `true` when Claude Code is already continuing as a result of a stop hook. Check this value or process the transcript to prevent Claude Code from running indefinitely.¶

```json theme={null}¶
{¶
"session_id": "abc123",¶
"transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",¶
"cwd": "/Users/...",¶
"permission_mode": "default",¶
"hook_event_name": "Stop",¶
"stop_hook_active": true¶
}¶
```¶

#### Stop decision control¶

`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:¶

| Field | Description |¶
| :--------- | :------------------------------------------------------------------------- |¶
| `decision` | `"block"` prevents Claude from stopping. Omit to allow Claude to stop |¶
| `reason` | Required when `decision` is `"block"`. Tells Claude why it should continue |¶

```json theme={null}¶
{¶
"decision": "block",¶
"reason": "Must be provided when Claude is blocked from stopping"¶
}¶
```¶

### PreCompact¶

Runs before Claude Code is about to run a compact operation.¶

The matcher value indicates whether compaction was triggered manually or automatically:¶

| Matcher | When it fires |¶
| :------- | :------------------------------------------- |¶
| `manual` | `/compact` |¶
| `auto` | Auto-compact when the context window is full |¶

#### PreCompact input¶

In 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.¶

```json theme={null}¶
{¶
"session_id": "abc123",¶
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",¶
"cwd": "/Users/...",¶
"permission_mode": "default",¶
"hook_event_name": "PreCompact",¶
"trigger": "manual",¶
"custom_instructions": ""¶
}¶
```¶

### SessionEnd¶

Runs when a Claude Code session ends. Useful for cleanup tasks, logging session¶
statistics, or saving session state. Supports matchers to filter by exit reason.¶

The `reason` field in the hook input indicates why the session ended:¶

| Reason | Description |¶
| :---------------------------- | :----------------------------------------- |¶
| `clear` | Session cleared with `/clear` command |¶
| `logout` | User logged out |¶
| `prompt_input_exit` | User exited while prompt input was visible |¶
| `bypass_permissions_disabled` | Bypass permissions mode was disabled |¶
| `other` | Other exit reasons |¶

#### SessionEnd input¶

In 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.¶

```json theme={null}¶
{¶
"session_id": "abc123",¶
"transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",¶
"cwd": "/Users/...",¶
"permission_mode": "default",¶
"hook_event_name": "SessionEnd",¶
"reason": "other"¶
}¶
```¶

SessionEnd hooks have no decision control. They cannot block session termination but can perform cleanup tasks.¶

## Prompt-based hooks¶

In addition to Bash command hooks (`type: "command"`), Claude Code supports prompt-based hooks (`type: "prompt"`) that use an LLM to evaluate whether to allow or block an action. Prompt-based hooks work with the following events: `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `UserPromptSubmit`, `Stop`, and `SubagentStop`.¶

### How prompt-based hooks work¶

Instead of executing a Bash command, prompt-based hooks:¶

1. Send the hook input and your prompt to a Claude model, Haiku by default¶
2. The LLM responds with structured JSON containing a decision¶
3. Claude Code processes the decision automatically¶

### Prompt hook configuration¶

Set `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.¶

This `Stop` hook asks the LLM to evaluate whether all tasks are complete before allowing Claude to finish:¶

```json theme={null}¶
{¶
"hooks": {¶
"Stop": [¶
{¶
"hooks": [¶
{¶
"type": "prompt",¶
"prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."¶
}¶
]¶
}¶
]¶
}¶
}¶
```¶

| Field | Required | Description |¶
| :-------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |¶
| `type` | yes | Must be `"prompt"` |¶
| `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 |¶
| `model` | no | Model to use for evaluation. Defaults to a fast model |¶
| `timeout` | no | Timeout in seconds. Default: 30 |¶

### Response schema¶

The LLM must respond with JSON containing:¶

```json theme={null}¶
{¶
"ok": true | false,¶
"reason": "Explanation for the decision"¶
}¶
```¶

| Field | Description |¶
| :------- | :--------------------------------------------------------- |¶
| `ok` | `true` allows the action, `false` prevents it |¶
| `reason` | Required when `ok` is `false`. Explanation shown to Claude |¶

### Example: Multi-criteria Stop hook¶

This `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:¶

```json theme={null}¶
{¶
"hooks": {¶
"Stop": [¶
{¶
"hooks": [¶
{¶
"type": "prompt",¶
"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.",¶
"timeout": 30¶
}¶
]¶
}¶
]¶
}¶
}¶
```¶

## Agent-based hooks¶

Agent-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.¶

### How agent hooks work¶

When an agent hook fires:¶

1. Claude Code spawns a subagent with your prompt and the hook's JSON input¶
2. The subagent can use tools like Read, Grep, and Glob to investigate¶
3. After up to 50 turns, the subagent returns a structured `{ "ok": true/false }` decision¶
4. Claude Code processes the decision the same way as a prompt hook¶

Agent hooks are useful when verification requires inspecting actual files or test output, not just evaluating the hook input data alone.¶

### Agent hook configuration¶

Set `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:¶

| Field | Required | Description |¶
| :-------- | :------- | :------------------------------------------------------------------------------------------ |¶
| `type` | yes | Must be `"agent"` |¶
| `prompt` | yes | Prompt describing what to verify. Use `$ARGUMENTS` as a placeholder for the hook input JSON |¶
| `model` | no | Model to use. Defaults to a fast model |¶
| `timeout` | no | Timeout in seconds. Default: 60 |¶

The response schema is the same as prompt hooks: `{ "ok": true }` to allow or `{ "ok": false, "reason": "..." }` to block.¶

This `Stop` hook verifies that all unit tests pass before allowing Claude to finish:¶

```json theme={null}¶
{¶
"hooks": {¶
"Stop": [¶
{¶
"hooks": [¶
{¶
"type": "agent",¶
"prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",¶
"timeout": 120¶
}¶
]¶
}¶
]¶
}¶
}¶
```¶

## Run hooks in the background¶

By 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.¶

### Configure an async hook¶

Add `"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.¶

This 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:¶

```json theme={null}¶
{¶
"hooks": {¶
"PostToolUse": [¶
{¶
"matcher": "Write",¶
"hooks": [¶
{¶
"type": "command",¶
"command": "/path/to/run-tests.sh",¶
"async": true,¶
"timeout": 120¶
}¶
]¶
}¶
]¶
}¶
}¶
```¶

The `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.¶

### How async hooks execute¶

When 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.¶

After 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.¶

### Example: run tests after file changes¶

This 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`:¶

```bash theme={null}¶
#!/bin/bash¶
# run-tests-async.sh¶

# Read hook input from stdin¶
INPUT=$(cat)¶
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')¶

# Only run tests for source files¶
if [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.js ]]; then¶
exit 0¶
fi¶

# Run tests and report results via systemMessage¶
RESULT=$(npm test 2>&1)¶
EXIT_CODE=$?¶

if [ $EXIT_CODE -eq 0 ]; then¶
echo "{\"systemMessage\": \"Tests passed after editing $FILE_PATH\"}"¶
else¶
echo "{\"systemMessage\": \"Tests failed after editing $FILE_PATH: $RESULT\"}"¶
fi¶
```¶

Then add this configuration to `.claude/settings.json` in your project root. The `async: true` flag lets Claude keep working while tests run:¶

```json theme={null}¶
{¶
"hooks": {¶
"PostToolUse": [¶
{¶
"matcher": "Write|Edit",¶
"hooks": [¶
{¶
"type": "command",¶
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/run-tests-async.sh",¶
"async": true,¶
"timeout": 300¶
}¶
]¶
}¶
]¶
}¶
}¶
```¶

### Limitations¶

Async hooks have several constraints compared to synchronous hooks:¶

* Only `type: "command"` hooks support `async`. Prompt-based hooks cannot run asynchronously.¶
* Async hooks cannot block tool calls or return decisions. By the time the hook completes, the triggering action has already proceeded.¶
* Hook output is delivered on the next conversation turn. If the session is idle, the response waits until the next user interaction.¶
* Each execution creates a separate background process. There is no deduplication across multiple firings of the same async hook.¶

## Security considerations¶

### Disclaimer¶

Hooks run with your system user's full permissions.¶

<Warning>¶
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.¶
</Warning>¶

### Security best practices¶

Keep these practices in mind when writing hooks:¶

* **Validate and sanitize inputs**: never trust input data blindly¶
* **Always quote shell variables**: use `"$VAR"` not `$VAR`¶
* **Block path traversal**: check for `..` in file paths¶
* **Use absolute paths**: specify full paths for scripts, using `"$CLAUDE_PROJECT_DIR"` for the project root¶
* **Skip sensitive files**: avoid `.env`, `.git/`, keys, etc.¶

## Debug hooks¶

Run `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.¶

```¶
[DEBUG] Executing hooks for PostToolUse:Write¶
[DEBUG] Getting matching hook commands for PostToolUse with query: Write¶
[DEBUG] Found 1 hook matchers in settings¶
[DEBUG] Matched 1 hooks for query "Write"¶
[DEBUG] Found 1 hook commands to execute¶
[DEBUG] Executing hook command: <Your command> with timeout 600000ms¶
[DEBUG] Hook command completed with status 0: <Your stdout>¶
```¶

For 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.

Unified Diff

--- a/hooks.md
+++ b/hooks.md
@@ -4,15 +4,17 @@
 
 # Hooks reference
 
-> This page provides reference documentation for implementing hooks in Claude Code.
+> Reference for Claude Code hook events, configuration schema, JSON input/output formats, exit codes, async hooks, prompt hooks, and MCP tool hooks.
 
 <Tip>
-  For a quickstart guide with examples, see [Get started with Claude Code hooks](/en/hooks-guide).
+  For a quickstart guide with examples, see [Automate workflows with hooks](/en/hooks-guide).
 </Tip>
 
+Hooks 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.
+
 ## Hook lifecycle
 
-Hooks fire at specific points during a Claude Code session.
+Hooks 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:
 
 <div style={{maxWidth: "500px", margin: "0 auto"}}>
   <Frame>
@@ -20,48 +22,37 @@
   </Frame>
 </div>
 
-| Hook                 | When it fires                   |
-| :------------------- | :------------------------------ |
-| `SessionStart`       | Session begins or resumes       |
-| `UserPromptSubmit`   | User submits a prompt           |
-| `PreToolUse`         | Before tool execution           |
-| `PermissionRequest`  | When permission dialog appears  |
-| `PostToolUse`        | After tool succeeds             |
-| `PostToolUseFailure` | After tool fails                |
-| `SubagentStart`      | When spawning a subagent        |
-| `SubagentStop`       | When subagent finishes          |
-| `Stop`               | Claude finishes responding      |
-| `PreCompact`         | Before context compaction       |
-| `SessionEnd`         | Session terminates              |
-| `Notification`       | Claude Code sends notifications |
-
-## Configuration
-
-Claude Code hooks are configured in your [settings files](/en/settings):
-
-* `~/.claude/settings.json` - User settings
-* `.claude/settings.json` - Project settings
-* `.claude/settings.local.json` - Local project settings (not committed)
-* Managed policy settings
-
-<Note>
-  Enterprise administrators can use `allowManagedHooksOnly` to block user, project, and plugin hooks. See [Hook configuration](/en/settings#hook-configuration).
-</Note>
-
-### Structure
-
-Hooks are organized by matchers, where each matcher can have multiple hooks:
+The 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.
+
+| Event                | When it fires                                        |
+| :------------------- | :--------------------------------------------------- |
+| `SessionStart`       | When a session begins or resumes                     |
+| `UserPromptSubmit`   | When you submit a prompt, before Claude processes it |
+| `PreToolUse`         | Before a tool call executes. Can block it            |
+| `PermissionRequest`  | When a permission dialog appears                     |
+| `PostToolUse`        | After a tool call succeeds                           |
+| `PostToolUseFailure` | After a tool call fails                              |
+| `Notification`       | When Claude Code sends a notification                |
+| `SubagentStart`      | When a subagent is spawned                           |
+| `SubagentStop`       | When a subagent finishes                             |
+| `Stop`               | When Claude finishes responding                      |
+| `PreCompact`         | Before context compaction                            |
+| `SessionEnd`         | When a session terminates                            |
+
+### How a hook resolves
+
+To 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:
 
 ```json  theme={null}
 {
   "hooks": {
-    "EventName": [
+    "PreToolUse": [
       {
-        "matcher": "ToolPattern",
+        "matcher": "Bash",
         "hooks": [
           {
             "type": "command",
-            "command": "your-command-here"
+            "command": ".claude/hooks/block-rm.sh"
           }
         ]
       }
@@ -70,30 +61,114 @@
 }
 ```
 
-* **matcher**: Pattern to match tool names, case-sensitive (only applicable for
-  `PreToolUse`, `PermissionRequest`, and `PostToolUse`)
-  * Simple strings match exactly: `Write` matches only the Write tool
-  * Supports regex: `Edit|Write` or `Notebook.*`
-  * Use `*` to match all tools. You can also use empty string (`""`) or leave
-    `matcher` blank.
-* **hooks**: Array of hooks to execute when the pattern matches
-  * `type`: Hook execution type - `"command"` for bash commands or `"prompt"` for LLM-based evaluation
-  * `command`: (For `type: "command"`) The bash command to execute (can use `$CLAUDE_PROJECT_DIR` environment variable)
-  * `prompt`: (For `type: "prompt"`) The prompt to send to the LLM for evaluation
-  * `timeout`: (Optional) How long a hook should run, in seconds, before canceling that specific hook
-
-For events like `UserPromptSubmit`, `Stop`, `SubagentStop`, and `Setup`
-that don't use matchers, you can omit the matcher field:
+The script reads the JSON input from stdin, extracts the command, and blocks it if it contains `rm -rf`:
+
+```bash  theme={null}
+#!/bin/bash
+# .claude/hooks/block-rm.sh
+COMMAND=$(jq -r '.tool_input.command')
+
+if echo "$COMMAND" | grep -q 'rm -rf'; then
+  echo '{"decision":"block","reason":"Destructive command blocked by hook"}'
+else
+  exit 0  # allow the command
+fi
+```
+
+Now suppose Claude Code decides to run `Bash "rm -rf /tmp/build"`. Here's what happens:
+
+<Frame>
+  <img src="https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=7c13f51ffcbc37d22a593b27e2f2de72" alt="Hook resolution flow: PreToolUse event fires, matcher checks for Bash match, hook handler runs, result returns to Claude Code" data-og-width="780" width="780" data-og-height="290" height="290" data-path="images/hook-resolution.svg" data-optimize="true" data-opv="3" srcset="https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=280&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=36a39a07e8bc1995dcb4639e09846905 280w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=560&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=6568d90c596c7605bbac2c325b0a0c86 560w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=840&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=255a6f68b9475a0e41dbde7b88002dad 840w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=1100&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=dcecf8d5edc88cd2bc49deb006d5760d 1100w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=1650&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=04fe51bf69ae375e9fd517f18674e35f 1650w, https://mintcdn.com/claude-code/s7NM0vfd_wres2nf/images/hook-resolution.svg?w=2500&fit=max&auto=format&n=s7NM0vfd_wres2nf&q=85&s=b1b76e0b77fddb5c7fa7bf302dacd80b 2500w" />
+</Frame>
+
+<Steps>
+  <Step title="Event fires">
+    The `PreToolUse` event fires. Claude Code sends the tool input as JSON on stdin to the hook:
+
+    ```json  theme={null}
+    { "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... }
+    ```
+  </Step>
+
+  <Step title="Matcher checks">
+    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.
+  </Step>
+
+  <Step title="Hook handler runs">
+    The script extracts `"rm -rf /tmp/build"` from the input and finds `rm -rf`, so it prints a decision to stdout:
+
+    ```json  theme={null}
+    { "decision": "block", "reason": "Destructive command blocked by hook" }
+    ```
+
+    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.
+  </Step>
+
+  <Step title="Claude Code acts on the result">
+    Claude Code reads the JSON decision, blocks the tool call, and shows Claude the reason.
+  </Step>
+</Steps>
+
+The [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.
+
+## Configuration
+
+Hooks are defined in JSON settings files. The configuration has three levels of nesting:
+
+1. Choose a [hook event](#hook-events) to respond to, like `PreToolUse` or `Stop`
+2. Add a [matcher group](#matcher-patterns) to filter when it fires, like "only for the Bash tool"
+3. Define one or more [hook handlers](#hook-handler-fields) to run when matched
+
+See [How a hook resolves](#how-a-hook-resolves) above for a complete walkthrough with an annotated example.
+
+<Note>
+  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.
+</Note>
+
+### Hook locations
+
+Where you define a hook determines its scope:
+
+| Location                                                   | Scope                         | Shareable                          |
+| :--------------------------------------------------------- | :---------------------------- | :--------------------------------- |
+| `~/.claude/settings.json`                                  | All your projects             | No, local to your machine          |
+| `.claude/settings.json`                                    | Single project                | Yes, can be committed to the repo  |
+| `.claude/settings.local.json`                              | Single project                | No, gitignored                     |
+| Managed policy settings                                    | Organization-wide             | Yes, admin-controlled              |
+| [Plugin](/en/plugins) `hooks/hooks.json`                   | When plugin is enabled        | Yes, bundled with the plugin       |
+| [Skill](/en/skills) or [agent](/en/sub-agents) frontmatter | While the component is active | Yes, defined in the component file |
+
+For 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).
+
+### Matcher patterns
+
+The `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:
+
+| Event                                                                  | What the matcher filters  | Example matcher values                                                         |
+| :--------------------------------------------------------------------- | :------------------------ | :----------------------------------------------------------------------------- |
+| `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest` | tool name                 | `Bash`, `Edit\|Write`, `mcp__.*`                                               |
+| `SessionStart`                                                         | how the session started   | `startup`, `resume`, `clear`, `compact`                                        |
+| `SessionEnd`                                                           | why the session ended     | `clear`, `logout`, `prompt_input_exit`, `bypass_permissions_disabled`, `other` |
+| `Notification`                                                         | notification type         | `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog`       |
+| `SubagentStart`                                                        | agent type                | `Bash`, `Explore`, `Plan`, or custom agent names                               |
+| `PreCompact`                                                           | what triggered compaction | `manual`, `auto`                                                               |
+| `SubagentStop`                                                         | agent type                | same values as `SubagentStart`                                                 |
+| `UserPromptSubmit`, `Stop`                                             | no matcher support        | always fires on every occurrence                                               |
+
+The 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.
+
+This example runs a linting script only when Claude writes or edits a file:
 
 ```json  theme={null}
 {
   "hooks": {
-    "UserPromptSubmit": [
+    "PostToolUse": [
       {
+        "matcher": "Edit|Write",
         "hooks": [
           {
             "type": "command",
-            "command": "/path/to/prompt-validator.py"
+            "command": "/path/to/lint-check.sh"
           }
         ]
       }
@@ -102,22 +177,44 @@
 }
 ```
 
-### Project-Specific Hook Scripts
-
-You can use the environment variable `CLAUDE_PROJECT_DIR` (only available when
-Claude Code spawns the hook command) to reference scripts stored in your project,
-ensuring they work regardless of Claude's current directory:
+`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.
+
+#### Match MCP tools
+
+[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.
+
+MCP tools follow the naming pattern `mcp__<server>__<tool>`, for example:
+
+* `mcp__memory__create_entities`: Memory server's create entities tool
+* `mcp__filesystem__read_file`: Filesystem server's read file tool
+* `mcp__github__search_repositories`: GitHub server's search tool
+
+Use regex patterns to target specific MCP tools or groups of tools:
+
+* `mcp__memory__.*` matches all tools from the `memory` server
+* `mcp__.*__write.*` matches any tool containing "write" from any server
+
+This example logs all memory server operations and validates write operations from any MCP server:
 
 ```json  theme={null}
 {
   "hooks": {
-    "PostToolUse": [
+    "PreToolUse": [
       {
-        "matcher": "Write|Edit",
+        "matcher": "mcp__memory__.*",
         "hooks": [
           {
             "type": "command",
-            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-style.sh"
+            "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
+          }
+        ]
+      },
+      {
+        "matcher": "mcp__.*__write.*",
+        "hooks": [
+          {
+            "type": "command",
+            "command": "/home/user/scripts/validate-mcp-write.py"
           }
         ]
       }
@@ -126,62 +223,113 @@
 }
 ```
 
-### Plugin hooks
-
-[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.
-
-**How plugin hooks work**:
-
-* 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.
-* When a plugin is enabled, its hooks are merged with user and project hooks
-* Multiple hooks from different sources can respond to the same event
-* Plugin hooks use the `${CLAUDE_PLUGIN_ROOT}` environment variable to reference plugin files
-
-**Example plugin hook configuration**:
-
-```json  theme={null}
-{
-  "description": "Automatic code formatting",
-  "hooks": {
-    "PostToolUse": [
-      {
-        "matcher": "Write|Edit",
-        "hooks": [
+### Hook handler fields
+
+Each 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:
+
+* **[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.
+* **[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).
+* **[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).
+
+#### Common fields
+
+These fields apply to all hook types:
+
+| Field           | Required | Description                                                                                                                                   |
+| :-------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------- |
+| `type`          | yes      | `"command"`, `"prompt"`, or `"agent"`                                                                                                         |
+| `timeout`       | no       | Seconds before canceling. Defaults: 600 for command, 30 for prompt, 60 for agent                                                              |
+| `statusMessage` | no       | Custom spinner message displayed while the hook runs                                                                                          |
+| `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) |
+
+#### Command hook fields
+
+In addition to the [common fields](#common-fields), command hooks accept these fields:
+
+| Field     | Required | Description                                                                                                         |
+| :-------- | :------- | :------------------------------------------------------------------------------------------------------------------ |
+| `command` | yes      | Shell command to execute                                                                                            |
+| `async`   | no       | If `true`, runs in the background without blocking. See [Run hooks in the background](#run-hooks-in-the-background) |
+
+#### Prompt and agent hook fields
+
+In addition to the [common fields](#common-fields), prompt and agent hooks accept these fields:
+
+| Field    | Required | Description                                                                                 |
+| :------- | :------- | :------------------------------------------------------------------------------------------ |
+| `prompt` | yes      | Prompt text to send to the model. Use `$ARGUMENTS` as a placeholder for the hook input JSON |
+| `model`  | no       | Model to use for evaluation. Defaults to a fast model                                       |
+
+All 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.
+
+### Reference scripts by path
+
+Use environment variables to reference hook scripts relative to the project or plugin root, regardless of the working directory when the hook runs:
+
+* `$CLAUDE_PROJECT_DIR`: the project root. Wrap in quotes to handle paths with spaces.
+* `${CLAUDE_PLUGIN_ROOT}`: the plugin's root directory, for scripts bundled with a [plugin](/en/plugins).
+
+<Tabs>
+  <Tab title="Project scripts">
+    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:
+
+    ```json  theme={null}
+    {
+      "hooks": {
+        "PostToolUse": [
           {
-            "type": "command",
-            "command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",
-            "timeout": 30
+            "matcher": "Write|Edit",
+            "hooks": [
+              {
+                "type": "command",
+                "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-style.sh"
+              }
+            ]
           }
         ]
       }
-    ]
-  }
-}
-```
-
-<Note>
-  Plugin hooks use the same format as regular hooks with an optional `description` field to explain the hook's purpose.
-</Note>
-
-<Note>
-  Plugin hooks run alongside your custom hooks. If multiple hooks match an event, they all execute in parallel.
-</Note>
-
-**Environment variables for plugins**:
-
-* `${CLAUDE_PLUGIN_ROOT}`: Absolute path to the plugin directory
-* `${CLAUDE_PROJECT_DIR}`: Project root directory (same as for project hooks)
-* All standard environment variables are available
-
-See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.
+    }
+    ```
+  </Tab>
+
+  <Tab title="Plugin scripts">
+    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.
+
+    This example runs a formatting script bundled with the plugin:
+
+    ```json  theme={null}
+    {
+      "description": "Automatic code formatting",
+      "hooks": {
+        "PostToolUse": [
+          {
+            "matcher": "Write|Edit",
+            "hooks": [
+              {
+                "type": "command",
+                "command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",
+                "timeout": 30
+              }
+            ]
+          }
+        ]
+      }
+    }
+    ```
+
+    See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.
+  </Tab>
+</Tabs>
 
 ### Hooks in skills and agents
 
 In 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.
 
-**Supported events**: `PreToolUse`, `PostToolUse`, and `Stop`
-
-**Example in a Skill**:
+All hook events are supported. For subagents, `Stop` hooks are automatically converted to `SubagentStop` since that is the event that fires when a subagent completes.
+
+Hooks use the same configuration format as settings-based hooks but are scoped to the component's lifetime and cleaned up when it finishes.
+
+This skill defines a `PreToolUse` hook that runs a security validation script before each `Bash` command:
 
 ```yaml  theme={null}
 ---
@@ -196,198 +344,593 @@
 ---
 ```
 
-**Example in an agent**:
-
-```yaml  theme={null}
----
-name: code-reviewer
-description: Review code changes
-hooks:
-  PostToolUse:
-    - matcher: "Edit|Write"
-      hooks:
-        - type: command
-          command: "./scripts/run-linter.sh"
----
-```
-
-Component-scoped hooks follow the same configuration format as settings-based hooks but are automatically cleaned up when the component finishes executing.
-
-**Additional option for skills:**
-
-* `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.
-
-## Prompt-Based Hooks
-
-In 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.
-
-### How prompt-based hooks work
-
-Instead of executing a bash command, prompt-based hooks:
-
-1. Send the hook input and your prompt to a fast LLM (Haiku)
-2. The LLM responds with structured JSON containing a decision
-3. Claude Code processes the decision automatically
-
-### Configuration
-
-```json  theme={null}
-{
-  "hooks": {
-    "Stop": [
-      {
-        "hooks": [
-          {
-            "type": "prompt",
-            "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."
-          }
-        ]
-      }
-    ]
-  }
-}
-```
-
-**Fields:**
-
-* `type`: Must be `"prompt"`
-* `prompt`: 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
-* `timeout`: (Optional) Timeout in seconds (default: 30 seconds)
-
-### Response schema
-
-The LLM must respond with JSON containing:
-
-```json  theme={null}
-{
-  "ok": true | false,
-  "reason": "Explanation for the decision"
-}
-```
-
-**Response fields:**
-
-* `ok`: `true` allows the action, `false` prevents it
-* `reason`: Required when `ok` is `false`. Explanation shown to Claude
-
-### Supported hook events
-
-Prompt-based hooks work with any hook event, but are most useful for:
-
-* **Stop**: Intelligently decide if Claude should continue working
-* **SubagentStop**: Evaluate if a subagent has completed its task
-* **UserPromptSubmit**: Validate user prompts with LLM assistance
-* **PreToolUse**: Make context-aware permission decisions
-* **PermissionRequest**: Intelligently allow or deny permission dialogs
-
-### Example: Intelligent Stop hook
-
-```json  theme={null}
-{
-  "hooks": {
-    "Stop": [
-      {
-        "hooks": [
-          {
-            "type": "prompt",
-            "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.",
-            "timeout": 30
-          }
-        ]
-      }
-    ]
-  }
-}
-```
-
-### Example: SubagentStop with custom logic
-
-```json  theme={null}
-{
-  "hooks": {
-    "SubagentStop": [
-      {
-        "hooks": [
-          {
-            "type": "prompt",
-            "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."
-          }
-        ]
-      }
-    ]
-  }
-}
-```
-
-### Comparison with bash command hooks
-
-| Feature               | Bash Command Hooks      | Prompt-Based Hooks             |
-| --------------------- | ----------------------- | ------------------------------ |
-| **Execution**         | Runs bash script        | Queries LLM                    |
-| **Decision logic**    | You implement in code   | LLM evaluates context          |
-| **Setup complexity**  | Requires script file    | Configure prompt               |
-| **Context awareness** | Limited to script logic | Natural language understanding |
-| **Performance**       | Fast (local execution)  | Slower (API call)              |
-| **Use case**          | Deterministic rules     | Context-aware decisions        |
-
-### Best practices
-
-* **Be specific in prompts**: Clearly state what you want the LLM to evaluate
-* **Include decision criteria**: List the factors the LLM should consider
-* **Test your prompts**: Verify the LLM makes correct decisions for your use cases
-* **Set appropriate timeouts**: Default is 30 seconds, adjust if needed
-* **Use for complex decisions**: Bash hooks are better for simple, deterministic rules
-
-See the [plugin components reference](/en/plugins-reference#hooks) for details on creating plugin hooks.
-
-## Hook Events
+Agents use the same format in their YAML frontmatter.
+
+### The `/hooks` menu
+
+Type `/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.
+
+Each hook in the menu is labeled with a bracket prefix indicating its source:
+
+* `[User]`: from `~/.claude/settings.json`
+* `[Project]`: from `.claude/settings.json`
+* `[Local]`: from `.claude/settings.local.json`
+* `[Plugin]`: from a plugin's `hooks/hooks.json`, read-only
+
+### Disable or remove hooks
+
+To remove a hook, delete its entry from the settings JSON file, or use the `/hooks` menu and select the hook to delete it.
+
+To 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.
+
+Direct 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.
+
+## Hook input and output
+
+Hooks 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.
+
+### Common input fields
+
+All hook events receive these fields via stdin as JSON, in addition to event-specific fields documented in each [hook event](#hook-events) section:
+
+| Field             | Description                                                                                                                        |
+| :---------------- | :--------------------------------------------------------------------------------------------------------------------------------- |
+| `session_id`      | Current session identifier                                                                                                         |
+| `transcript_path` | Path to conversation JSON                                                                                                          |
+| `cwd`             | Current working directory when the hook is invoked                                                                                 |
+| `permission_mode` | Current [permission mode](/en/iam#permission-modes): `"default"`, `"plan"`, `"acceptEdits"`, `"dontAsk"`, or `"bypassPermissions"` |
+| `hook_event_name` | Name of the event that fired                                                                                                       |
+
+For example, a `PreToolUse` hook for a Bash command receives this on stdin:
+
+```json  theme={null}
+{
+  "session_id": "abc123",
+  "transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
+  "cwd": "/home/user/my-project",
+  "permission_mode": "default",
+  "hook_event_name": "PreToolUse",
+  "tool_name": "Bash",
+  "tool_input": {
+    "command": "npm test"
+  }
+}
+```
+
+The `tool_name` and `tool_input` fields are event-specific. Each [hook event](#hook-events) section documents the additional fields for that event.
+
+### Exit code output
+
+The exit code from your hook command tells Claude Code whether the action should proceed, be blocked, or be ignored.
+
+**Exit 0** means success. Claude Code parses stdout for [JSON output fields](#json-output) like `decision` or `reason`. 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.
+
+**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.
+
+**Any other exit code** is a non-blocking error. stderr is shown in verbose mode (`Ctrl+O`) and execution continues.
+
+For example, a hook command script that blocks dangerous Bash commands:
+
+```bash  theme={null}
+#!/bin/bash
+# Reads JSON input from stdin, checks the command
+command=$(jq -r '.tool_input.command' < /dev/stdin)
+
+if [[ "$command" == rm* ]]; then
+  echo "Blocked: rm commands are not allowed" >&2
+  exit 2  # Blocking error: tool call is prevented
+fi
+
+exit 0  # Success: tool call proceeds
+```
+
+#### Exit code 2 behavior per event
+
+Exit 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.
+
+| Hook event           | Can block? | What happens on exit 2                                    |
+| :------------------- | :--------- | :-------------------------------------------------------- |
+| `PreToolUse`         | Yes        | Blocks the tool call                                      |
+| `PermissionRequest`  | Yes        | Denies the permission                                     |
+| `UserPromptSubmit`   | Yes        | Blocks prompt processing and erases the prompt            |
+| `Stop`               | Yes        | Prevents Claude from stopping, continues the conversation |
+| `SubagentStop`       | Yes        | Prevents the subagent from stopping                       |
+| `PostToolUse`        | No         | Shows stderr to Claude (tool already ran)                 |
+| `PostToolUseFailure` | No         | Shows stderr to Claude (tool already failed)              |
+| `Notification`       | No         | Shows stderr to user only                                 |
+| `SubagentStart`      | No         | Shows stderr to user only                                 |
+| `SessionStart`       | No         | Shows stderr to user only                                 |
+| `SessionEnd`         | No         | Shows stderr to user only                                 |
+| `PreCompact`         | No         | Shows stderr to user only                                 |
+
+### JSON output
+
+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.
+
+Instead of relying on exit codes alone, hooks can print JSON to stdout on exit 0. Claude Code reads specific fields from this JSON to decide what to do next.
+
+Your 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.
+
+The JSON object has two parts:
+
+* **Top-level fields** like `continue` and `decision` work across all events. These are listed in the table below.
+* **`hookSpecificOutput`** is a nested object for event-specific fields like `permissionDecision` or `additionalContext`. It requires a `hookEventName` field set to the event name, like `"PreToolUse"` or `"Stop"`. Each event's decision control section under [Hook events](#hook-events) documents what fields go here.
+
+| Field            | Default | Description                                                                                                                                           |
+| :--------------- | :------ | :---------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `continue`       | `true`  | If `false`, Claude stops processing entirely after the hook runs. Takes precedence over event-specific fields like `decision` or `permissionDecision` |
+| `stopReason`     | none    | Message shown to the user when `continue` is `false`. Not shown to Claude                                                                             |
+| `suppressOutput` | `false` | If `true`, hides stdout from verbose mode output                                                                                                      |
+| `systemMessage`  | none    | Warning message shown to the user                                                                                                                     |
+
+This example uses a top-level field to stop Claude:
+
+```json  theme={null}
+{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }
+```
+
+This example uses `hookSpecificOutput` to deny a PreToolUse tool call:
+
+```json  theme={null}
+{
+  "hookSpecificOutput": {
+    "hookEventName": "PreToolUse",
+    "permissionDecision": "deny",
+    "permissionDecisionReason": "Database writes are not allowed"
+  }
+}
+```
+
+For 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).
+
+## Hook events
+
+Each 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.
+
+### SessionStart
+
+Runs 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.
+
+SessionStart runs on every session, so keep these hooks fast.
+
+The matcher value corresponds to how the session was initiated:
+
+| Matcher   | When it fires                          |
+| :-------- | :------------------------------------- |
+| `startup` | New session                            |
+| `resume`  | `--resume`, `--continue`, or `/resume` |
+| `clear`   | `/clear`                               |
+| `compact` | Auto or manual compaction              |
+
+#### SessionStart input
+
+In 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.
+
+```json  theme={null}
+{
+  "session_id": "abc123",
+  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
+  "cwd": "/Users/...",
+  "permission_mode": "default",
+  "hook_event_name": "SessionStart",
+  "source": "startup",
+  "model": "claude-sonnet-4-5-20250929"
+}
+```
+
+#### SessionStart decision control
+
+Any 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:
+
+| Field               | Description                                                               |
+| :------------------ | :------------------------------------------------------------------------ |
+| `additionalContext` | String added to Claude's context. Multiple hooks' values are concatenated |
+
+```json  theme={null}
+{
+  "hookSpecificOutput": {
+    "hookEventName": "SessionStart",
+    "additionalContext": "My additional context here"
+  }
+}
+```
+
+#### Persist environment variables
+
+SessionStart 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.
+
+To set individual environment variables, write `export` statements to `CLAUDE_ENV_FILE`. Use append (`>>`) to preserve variables set by other hooks:
+
+```bash  theme={null}
+#!/bin/bash
+
+if [ -n "$CLAUDE_ENV_FILE" ]; then
+  echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
+  echo 'export DEBUG_LOG=true' >> "$CLAUDE_ENV_FILE"
+  echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
+fi
+
+exit 0
+```
+
+To capture all environment changes from setup commands, compare the exported variables before and after:
+
+```bash  theme={null}
+#!/bin/bash
+
+ENV_BEFORE=$(export -p | sort)
+
+# Run your setup commands that modify the environment
+source ~/.nvm/nvm.sh
+nvm use 20
+
+if [ -n "$CLAUDE_ENV_FILE" ]; then
+  ENV_AFTER=$(export -p | sort)
+  comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"
+fi
+
+exit 0
+```
+
+Any variables written to this file will be available in all subsequent Bash commands that Claude Code executes during the session.
+
+<Note>
+  `CLAUDE_ENV_FILE` is available for SessionStart hooks. Other hook types do not have access to this variable.
+</Note>
+
+### UserPromptSubmit
+
+Runs when the user submits a prompt, before Claude processes it. This allows you
+to add additional context based on the prompt/conversation, validate prompts, or
+block certain types of prompts.
+
+#### UserPromptSubmit input
+
+In addition to the [common input fields](#common-input-fields), UserPromptSubmit hooks receive the `prompt` field containing the text the user submitted.
+
+```json  theme={null}
+{
+  "session_id": "abc123",
+  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
+  "cwd": "/Users/...",
+  "permission_mode": "default",
+  "hook_event_name": "UserPromptSubmit",
+  "prompt": "Write a function to calculate the factorial of a number"
+}
+```
+
+#### UserPromptSubmit decision control
+
+`UserPromptSubmit` hooks can control whether a user prompt is processed and add context. All [JSON output fields](#json-output) are available.
+
+There are two ways to add context to the conversation on exit code 0:
+
+* **Plain text stdout**: any non-JSON text written to stdout is added as context
+* **JSON with `additionalContext`**: use the JSON format below for more control. The `additionalContext` field is added as context
+
+Plain stdout is shown as hook output in the transcript. The `additionalContext` field is added more discretely.
+
+To block a prompt, return a JSON object with `decision` set to `"block"`:
+
+| Field               | Description                                                                                                        |
+| :------------------ | :----------------------------------------------------------------------------------------------------------------- |
+| `decision`          | `"block"` prevents the prompt from being processed and erases it from context. Omit to allow the prompt to proceed |
+| `reason`            | Shown to the user when `decision` is `"block"`. Not added to context                                               |
+| `additionalContext` | String added to Claude's context                                                                                   |
+
+```json  theme={null}
+{
+  "decision": "block",
+  "reason": "Explanation for decision",
+  "hookSpecificOutput": {
+    "hookEventName": "UserPromptSubmit",
+    "additionalContext": "My additional context here"
+  }
+}
+```
+
+<Note>
+  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
+  block prompts or want more structured control.
+</Note>
 
 ### PreToolUse
 
-Runs after Claude creates tool parameters and before processing the tool call.
-
-**Common matchers:**
-
-* `Task` - Subagent tasks (see [subagents documentation](/en/sub-agents))
-* `Bash` - Shell commands
-* `Glob` - File pattern matching
-* `Grep` - Content search
-* `Read` - File reading
-* `Edit` - File editing
-* `Write` - File writing
-* `WebFetch`, `WebSearch` - Web operations
+Runs 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).
 
 Use [PreToolUse decision control](#pretooluse-decision-control) to allow, deny, or ask for permission to use the tool.
+
+#### PreToolUse input
+
+In 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:
+
+##### Bash
+
+Executes shell commands.
+
+| Field               | Type    | Example            | Description                                   |
+| :------------------ | :------ | :----------------- | :-------------------------------------------- |
+| `command`           | string  | `"npm test"`       | The shell command to execute                  |
+| `description`       | string  | `"Run test suite"` | Optional description of what the command does |
+| `timeout`           | number  | `120000`           | Optional timeout in milliseconds              |
+| `run_in_background` | boolean | `false`            | Whether to run the command in background      |
+
+##### Write
+
+Creates or overwrites a file.
+
+| Field       | Type   | Example               | Description                        |
+| :---------- | :----- | :-------------------- | :--------------------------------- |
+| `file_path` | string | `"/path/to/file.txt"` | Absolute path to the file to write |
+| `content`   | string | `"file content"`      | Content to write to the file       |
+
+##### Edit
+
+Replaces a string in an existing file.
+
+| Field         | Type    | Example               | Description                        |
+| :------------ | :------ | :-------------------- | :--------------------------------- |
+| `file_path`   | string  | `"/path/to/file.txt"` | Absolute path to the file to edit  |
+| `old_string`  | string  | `"original text"`     | Text to find and replace           |
+| `new_string`  | string  | `"replacement text"`  | Replacement text                   |
+| `replace_all` | boolean | `false`               | Whether to replace all occurrences |
+
+##### Read
+
+Reads file contents.
+
+| Field       | Type   | Example               | Description                                |
+| :---------- | :----- | :-------------------- | :----------------------------------------- |
+| `file_path` | string | `"/path/to/file.txt"` | Absolute path to the file to read          |
+| `offset`    | number | `10`                  | Optional line number to start reading from |
+| `limit`     | number | `50`                  | Optional number of lines to read           |
+
+##### Glob
+
+Finds files matching a glob pattern.
+
+| Field     | Type   | Example          | Description                                                            |
+| :-------- | :----- | :--------------- | :--------------------------------------------------------------------- |
+| `pattern` | string | `"**/*.ts"`      | Glob pattern to match files against                                    |
+| `path`    | string | `"/path/to/dir"` | Optional directory to search in. Defaults to current working directory |
+
+##### Grep
+
+Searches file contents with regular expressions.
+
+| Field         | Type    | Example          | Description                                                                           |
+| :------------ | :------ | :--------------- | :------------------------------------------------------------------------------------ |
+| `pattern`     | string  | `"TODO.*fix"`    | Regular expression pattern to search for                                              |
+| `path`        | string  | `"/path/to/dir"` | Optional file or directory to search in                                               |
+| `glob`        | string  | `"*.ts"`         | Optional glob pattern to filter files                                                 |
+| `output_mode` | string  | `"content"`      | `"content"`, `"files_with_matches"`, or `"count"`. Defaults to `"files_with_matches"` |
+| `-i`          | boolean | `true`           | Case insensitive search                                                               |
+| `multiline`   | boolean | `false`          | Enable multiline matching                                                             |
+
+##### WebFetch
+
+Fetches and processes web content.
+
+| Field    | Type   | Example                       | Description                          |
+| :------- | :----- | :---------------------------- | :----------------------------------- |
+| `url`    | string | `"https://example.com/api"`   | URL to fetch content from            |
+| `prompt` | string | `"Extract the API endpoints"` | Prompt to run on the fetched content |
+
+##### WebSearch
+
+Searches the web.
+
+| Field             | Type   | Example                        | Description                                       |
+| :---------------- | :----- | :----------------------------- | :------------------------------------------------ |
+| `query`           | string | `"react hooks best practices"` | Search query                                      |
+| `allowed_domains` | array  | `["docs.example.com"]`         | Optional: only include results from these domains |
+| `blocked_domains` | array  | `["spam.example.com"]`         | Optional: exclude results from these domains      |
+
+##### Task
+
+Spawns a [subagent](/en/sub-agents).
+
+| Field           | Type   | Example                    | Description                                  |
+| :-------------- | :----- | :------------------------- | :------------------------------------------- |
+| `prompt`        | string | `"Find all API endpoints"` | The task for the agent to perform            |
+| `description`   | string | `"Find API endpoints"`     | Short description of the task                |
+| `subagent_type` | string | `"Explore"`                | Type of specialized agent to use             |
+| `model`         | string | `"sonnet"`                 | Optional model alias to override the default |
+
+#### PreToolUse decision control
+
+`PreToolUse` hooks can control whether a tool call proceeds. In addition to the [JSON output fields](#json-output) available to all hooks, your hook script can return a `hookSpecificOutput` object with these event-specific fields:
+
+| Field                      | Description                                                                                                                                      |
+| :------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |
+| `permissionDecision`       | `"allow"` bypasses the permission system, `"deny"` prevents the tool call, `"ask"` prompts the user to confirm                                   |
+| `permissionDecisionReason` | For `"allow"` and `"ask"`, shown to the user but not Claude. For `"deny"`, shown to Claude                                                       |
+| `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 |
+| `additionalContext`        | String added to Claude's context before the tool executes                                                                                        |
+
+```json  theme={null}
+{
+  "hookSpecificOutput": {
+    "hookEventName": "PreToolUse",
+    "permissionDecision": "allow",
+    "permissionDecisionReason": "My reason here",
+    "updatedInput": {
+      "field_to_modify": "new value"
+    },
+    "additionalContext": "Current environment: production. Proceed with caution."
+  }
+}
+```
+
+<Note>
+  The `decision` and `reason` fields are deprecated for PreToolUse hooks.
+  Use `hookSpecificOutput.permissionDecision` and
+  `hookSpecificOutput.permissionDecisionReason` instead. The deprecated fields
+  `"approve"` and `"block"` map to `"allow"` and `"deny"` respectively.
+</Note>
 
 ### PermissionRequest
 
 Runs when the user is shown a permission dialog.
 Use [PermissionRequest decision control](#permissionrequest-decision-control) to allow or deny on behalf of the user.
 
-Recognizes the same matcher values as PreToolUse.
+Matches on tool name, same values as PreToolUse.
+
+#### PermissionRequest input
+
+PermissionRequest 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.
+
+```json  theme={null}
+{
+  "session_id": "abc123",
+  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
+  "cwd": "/Users/...",
+  "permission_mode": "default",
+  "hook_event_name": "PermissionRequest",
+  "tool_name": "Bash",
+  "tool_input": {
+    "command": "rm -rf node_modules",
+    "description": "Remove node_modules directory"
+  },
+  "permission_suggestions": [
+    { "type": "toolAlwaysAllow", "tool": "Bash" }
+  ]
+}
+```
+
+#### PermissionRequest decision control
+
+`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:
+
+| Field                | Description                                                                                                    |
+| :------------------- | :------------------------------------------------------------------------------------------------------------- |
+| `behavior`           | `"allow"` grants the permission, `"deny"` denies it                                                            |
+| `updatedInput`       | For `"allow"` only: modifies the tool's input parameters before execution                                      |
+| `updatedPermissions` | For `"allow"` only: applies permission rule updates, equivalent to the user selecting an "always allow" option |
+| `message`            | For `"deny"` only: tells Claude why the permission was denied                                                  |
+| `interrupt`          | For `"deny"` only: if `true`, stops Claude                                                                     |
+
+```json  theme={null}
+{
+  "hookSpecificOutput": {
+    "hookEventName": "PermissionRequest",
+    "decision": {
+      "behavior": "allow",
+      "updatedInput": {
+        "command": "npm run lint"
+      }
+    }
+  }
+}
+```
 
 ### PostToolUse
 
 Runs immediately after a tool completes successfully.
 
-Recognizes the same matcher values as PreToolUse.
+Matches on tool name, same values as PreToolUse.
+
+#### PostToolUse input
+
+`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.
+
+```json  theme={null}
+{
+  "session_id": "abc123",
+  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
+  "cwd": "/Users/...",
+  "permission_mode": "default",
+  "hook_event_name": "PostToolUse",
+  "tool_name": "Write",
+  "tool_input": {
+    "file_path": "/path/to/file.txt",
+    "content": "file content"
+  },
+  "tool_response": {
+    "filePath": "/path/to/file.txt",
+    "success": true
+  },
+  "tool_use_id": "toolu_01ABC123..."
+}
+```
+
+#### PostToolUse decision control
+
+`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:
+
+| Field                  | Description                                                                                |
+| :--------------------- | :----------------------------------------------------------------------------------------- |
+| `decision`             | `"block"` prompts Claude with the `reason`. Omit to allow the action to proceed            |
+| `reason`               | Explanation shown to Claude when `decision` is `"block"`                                   |
+| `additionalContext`    | Additional context for Claude to consider                                                  |
+| `updatedMCPToolOutput` | For [MCP tools](#match-mcp-tools) only: replaces the tool's output with the provided value |
+
+```json  theme={null}
+{
+  "decision": "block",
+  "reason": "Explanation for decision",
+  "hookSpecificOutput": {
+    "hookEventName": "PostToolUse",
+    "additionalContext": "Additional information for Claude"
+  }
+}
+```
+
+### PostToolUseFailure
+
+Runs 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.
+
+Matches on tool name, same values as PreToolUse.
+
+#### PostToolUseFailure input
+
+PostToolUseFailure hooks receive the same `tool_name` and `tool_input` fields as PostToolUse, along with error information as top-level fields:
+
+```json  theme={null}
+{
+  "session_id": "abc123",
+  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
+  "cwd": "/Users/...",
+  "permission_mode": "default",
+  "hook_event_name": "PostToolUseFailure",
+  "tool_name": "Bash",
+  "tool_input": {
+    "command": "npm test",
+    "description": "Run test suite"
+  },
+  "tool_use_id": "toolu_01ABC123...",
+  "error": "Command exited with non-zero status code 1",
+  "is_interrupt": false
+}
+```
+
+| Field          | Description                                                                     |
+| :------------- | :------------------------------------------------------------------------------ |
+| `error`        | String describing what went wrong                                               |
+| `is_interrupt` | Optional boolean indicating whether the failure was caused by user interruption |
+
+#### PostToolUseFailure decision control
+
+`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:
+
+| Field               | Description                                                   |
+| :------------------ | :------------------------------------------------------------ |
+| `additionalContext` | Additional context for Claude to consider alongside the error |
+
+```json  theme={null}
+{
+  "hookSpecificOutput": {
+    "hookEventName": "PostToolUseFailure",
+    "additionalContext": "Additional information about the failure for Claude"
+  }
+}
+```
 
 ### Notification
 
-Runs when Claude Code sends notifications. Supports matchers to filter by notification type.
-
-**Common matchers:**
-
-* `permission_prompt` - Permission requests from Claude Code
-* `idle_prompt` - When Claude is waiting for user input (after 60+ seconds of idle time)
-* `auth_success` - Authentication success notifications
-* `elicitation_dialog` - When Claude Code needs input for MCP tool elicitation
-
-You can use matchers to run different hooks for different notification types, or omit the matcher to run hooks for all notifications.
-
-**Example: Different notifications for different types**
+Runs 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.
+
+Use 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:
 
 ```json  theme={null}
 {
@@ -416,266 +959,9 @@
 }
 ```
 
-### UserPromptSubmit
-
-Runs when the user submits a prompt, before Claude processes it. This allows you
-to add additional context based on the prompt/conversation, validate prompts, or
-block certain types of prompts.
-
-### Stop
-
-Runs when the main Claude Code agent has finished responding. Does not run if
-the stoppage occurred due to a user interrupt.
-
-### SubagentStop
-
-Runs when a Claude Code subagent (Task tool call) has finished responding.
-
-### PreCompact
-
-Runs before Claude Code is about to run a compact operation.
-
-**Matchers:**
-
-* `manual` - Invoked from `/compact`
-* `auto` - Invoked from auto-compact (due to full context window)
-
-### Setup
-
-Runs 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.
-
-<Note>
-  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.
-</Note>
-
-**Matchers:**
-
-* `init` - Invoked from `--init` or `--init-only` flags
-* `maintenance` - Invoked from `--maintenance` flag
-
-Setup hooks have access to the `CLAUDE_ENV_FILE` environment variable for persisting environment variables, similar to SessionStart hooks.
-
-### SessionStart
-
-Runs when Claude Code starts a new session or resumes an existing session (which
-currently 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.
-
-<Note>
-  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.
-</Note>
-
-**Matchers:**
-
-* `startup` - Invoked from startup
-* `resume` - Invoked from `--resume`, `--continue`, or `/resume`
-* `clear` - Invoked from `/clear`
-* `compact` - Invoked from auto or manual compact.
-
-#### Persisting environment variables
-
-SessionStart 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.
-
-**Example: Setting individual environment variables**
-
-```bash  theme={null}
-#!/bin/bash
-
-if [ -n "$CLAUDE_ENV_FILE" ]; then
-  echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
-  echo 'export API_KEY=your-api-key' >> "$CLAUDE_ENV_FILE"
-  echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
-fi
-
-exit 0
-```
-
-**Example: Persisting all environment changes from the hook**
-
-When your setup modifies the environment (for example, `nvm use`), capture and persist all changes by diffing the environment:
-
-```bash  theme={null}
-#!/bin/bash
-
-ENV_BEFORE=$(export -p | sort)
-
-# Run your setup commands that modify the environment
-source ~/.nvm/nvm.sh
-nvm use 20
-
-if [ -n "$CLAUDE_ENV_FILE" ]; then
-  ENV_AFTER=$(export -p | sort)
-  comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"
-fi
-
-exit 0
-```
-
-Any variables written to this file will be available in all subsequent bash commands that Claude Code executes during the session.
-
-<Note>
-  `CLAUDE_ENV_FILE` is only available for SessionStart hooks. Other hook types do not have access to this variable.
-</Note>
-
-### SessionEnd
-
-Runs when a Claude Code session ends. Useful for cleanup tasks, logging session
-statistics, or saving session state.
-
-The `reason` field in the hook input will be one of:
-
-* `clear` - Session cleared with /clear command
-* `logout` - User logged out
-* `prompt_input_exit` - User exited while prompt input was visible
-* `other` - Other exit reasons
-
-## Hook Input
-
-Hooks receive JSON data via stdin containing session information and
-event-specific data:
-
-```typescript  theme={null}
-{
-  // Common fields
-  session_id: string
-  transcript_path: string  // Path to conversation JSON
-  cwd: string              // The current working directory when the hook is invoked
-  permission_mode: string  // Current permission mode: "default", "plan", "acceptEdits", "dontAsk", or "bypassPermissions"
-
-  // Event-specific fields
-  hook_event_name: string
-  ...
-}
-```
-
-### PreToolUse Input
-
-The exact schema for `tool_input` depends on the tool. Here are examples for commonly hooked tools.
-
-#### Bash tool
-
-The Bash tool is the most commonly hooked tool for command validation:
-
-```json  theme={null}
-{
-  "session_id": "abc123",
-  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
-  "cwd": "/Users/...",
-  "permission_mode": "default",
-  "hook_event_name": "PreToolUse",
-  "tool_name": "Bash",
-  "tool_input": {
-    "command": "psql -c 'SELECT * FROM users'",
-    "description": "Query the users table",
-    "timeout": 120000
-  },
-  "tool_use_id": "toolu_01ABC123..."
-}
-```
-
-| Field               | Type    | Description                                   |
-| :------------------ | :------ | :-------------------------------------------- |
-| `command`           | string  | The shell command to execute                  |
-| `description`       | string  | Optional description of what the command does |
-| `timeout`           | number  | Optional timeout in milliseconds              |
-| `run_in_background` | boolean | Whether to run the command in background      |
-
-#### Write tool
-
-```json  theme={null}
-{
-  "session_id": "abc123",
-  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
-  "cwd": "/Users/...",
-  "permission_mode": "default",
-  "hook_event_name": "PreToolUse",
-  "tool_name": "Write",
-  "tool_input": {
-    "file_path": "/path/to/file.txt",
-    "content": "file content"
-  },
-  "tool_use_id": "toolu_01ABC123..."
-}
-```
-
-| Field       | Type   | Description                        |
-| :---------- | :----- | :--------------------------------- |
-| `file_path` | string | Absolute path to the file to write |
-| `content`   | string | Content to write to the file       |
-
-#### Edit tool
-
-```json  theme={null}
-{
-  "session_id": "abc123",
-  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
-  "cwd": "/Users/...",
-  "permission_mode": "default",
-  "hook_event_name": "PreToolUse",
-  "tool_name": "Edit",
-  "tool_input": {
-    "file_path": "/path/to/file.txt",
-    "old_string": "original text",
-    "new_string": "replacement text"
-  },
-  "tool_use_id": "toolu_01ABC123..."
-}
-```
-
-| Field         | Type    | Description                                         |
-| :------------ | :------ | :-------------------------------------------------- |
-| `file_path`   | string  | Absolute path to the file to edit                   |
-| `old_string`  | string  | Text to find and replace                            |
-| `new_string`  | string  | Replacement text                                    |
-| `replace_all` | boolean | Whether to replace all occurrences (default: false) |
-
-#### Read tool
-
-```json  theme={null}
-{
-  "session_id": "abc123",
-  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
-  "cwd": "/Users/...",
-  "permission_mode": "default",
-  "hook_event_name": "PreToolUse",
-  "tool_name": "Read",
-  "tool_input": {
-    "file_path": "/path/to/file.txt"
-  },
-  "tool_use_id": "toolu_01ABC123..."
-}
-```
-
-| Field       | Type   | Description                                |
-| :---------- | :----- | :----------------------------------------- |
-| `file_path` | string | Absolute path to the file to read          |
-| `offset`    | number | Optional line number to start reading from |
-| `limit`     | number | Optional number of lines to read           |
-
-### PostToolUse Input
-
-The exact schema for `tool_input` and `tool_response` depends on the tool.
-
-```json  theme={null}
-{
-  "session_id": "abc123",
-  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
-  "cwd": "/Users/...",
-  "permission_mode": "default",
-  "hook_event_name": "PostToolUse",
-  "tool_name": "Write",
-  "tool_input": {
-    "file_path": "/path/to/file.txt",
-    "content": "file content"
-  },
-  "tool_response": {
-    "filePath": "/path/to/file.txt",
-    "success": true
-  },
-  "tool_use_id": "toolu_01ABC123..."
-}
-```
-
-### Notification Input
+#### Notification input
+
+In 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.
 
 ```json  theme={null}
 {
@@ -685,11 +971,24 @@
   "permission_mode": "default",
   "hook_event_name": "Notification",
   "message": "Claude needs your permission to use Bash",
+  "title": "Permission needed",
   "notification_type": "permission_prompt"
 }
 ```
 
-### UserPromptSubmit Input
+Notification 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:
+
+| Field               | Description                      |
+| :------------------ | :------------------------------- |
+| `additionalContext` | String added to Claude's context |
+
+### SubagentStart
+
+Runs 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/`).
+
+#### SubagentStart input
+
+In 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).
 
 ```json  theme={null}
 {
@@ -697,31 +996,34 @@
   "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
   "cwd": "/Users/...",
   "permission_mode": "default",
-  "hook_event_name": "UserPromptSubmit",
-  "prompt": "Write a function to calculate the factorial of a number"
-}
-```
-
-### Stop Input
-
-`stop_hook_active` 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.
-
-```json  theme={null}
-{
-  "session_id": "abc123",
-  "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
-  "cwd": "/Users/...",
-  "permission_mode": "default",
-  "hook_event_name": "Stop",
-  "stop_hook_active": true
-}
-```
-
-### SubagentStop Input
-
-Triggered 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.
+  "hook_event_name": "SubagentStart",
+  "agent_id": "agent-abc123",
+  "agent_type": "Explore"
+}
+```
+
+SubagentStart 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:
+
+| Field               | Description                            |
+| :------------------ | :------------------------------------- |
+| `additionalContext` | String added to the subagent's context |
+
+```json  theme={null}
+{
+  "hookSpecificOutput": {
+    "hookEventName": "SubagentStart",
+    "additionalContext": "Follow security guidelines for this task"
+  }
+}
+```
+
+### SubagentStop
+
+Runs when a Claude Code subagent has finished responding. Matches on agent type, same values as SubagentStart.
+
+#### SubagentStop input
+
+In addition to the [common input fields](#common-input-fields), SubagentStop hooks receive `stop_hook_active`, `agent_id`, `agent_type`, and `agent_transcript_path`. The `agent_type` field is the value used for matcher filtering. The `transcript_path` is the main session's transcript, while `agent_transcript_path` is the subagent's own transcript stored in a nested `subagents/` folder.
 
 ```json  theme={null}
 {
@@ -732,19 +1034,69 @@
   "hook_event_name": "SubagentStop",
   "stop_hook_active": false,
   "agent_id": "def456",
+  "agent_type": "Explore",
   "agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl"
 }
 ```
 
-### PreCompact Input
-
-For `manual`, `custom_instructions` comes from what the user passes into
-`/compact`. For `auto`, `custom_instructions` is empty.
+SubagentStop hooks use the same decision control format as [Stop hooks](#stop-decision-control).
+
+### Stop
+
+Runs when the main Claude Code agent has finished responding. Does not run if
+the stoppage occurred due to a user interrupt.
+
+#### Stop input
+
+In addition to the [common input fields](#common-input-fields), Stop hooks receive `stop_hook_active`. This field is `true` when Claude Code is already continuing as a result of a stop hook. Check this value or process the transcript to prevent Claude Code from running indefinitely.
 
 ```json  theme={null}
 {
   "session_id": "abc123",
   "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
+  "cwd": "/Users/...",
+  "permission_mode": "default",
+  "hook_event_name": "Stop",
+  "stop_hook_active": true
+}
+```
+
+#### Stop decision control
+
+`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:
+
+| Field      | Description                                                                |
+| :--------- | :------------------------------------------------------------------------- |
+| `decision` | `"block"` prevents Claude from stopping. Omit to allow Claude to stop      |
+| `reason`   | Required when `decision` is `"block"`. Tells Claude why it should continue |
+
+```json  theme={null}
+{
+  "decision": "block",
+  "reason": "Must be provided when Claude is blocked from stopping"
+}
+```
+
+### PreCompact
+
+Runs before Claude Code is about to run a compact operation.
+
+The matcher value indicates whether compaction was triggered manually or automatically:
+
+| Matcher  | When it fires                                |
+| :------- | :------------------------------------------- |
+| `manual` | `/compact`                                   |
+| `auto`   | Auto-compact when the context window is full |
+
+#### PreCompact input
+
+In 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.
+
+```json  theme={null}
+{
+  "session_id": "abc123",
+  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
+  "cwd": "/Users/...",
   "permission_mode": "default",
   "hook_event_name": "PreCompact",
   "trigger": "manual",
@@ -752,507 +1104,65 @@
 }
 ```
 
-### Setup Input
+### SessionEnd
+
+Runs when a Claude Code session ends. Useful for cleanup tasks, logging session
+statistics, or saving session state. Supports matchers to filter by exit reason.
+
+The `reason` field in the hook input indicates why the session ended:
+
+| Reason                        | Description                                |
+| :---------------------------- | :----------------------------------------- |
+| `clear`                       | Session cleared with `/clear` command      |
+| `logout`                      | User logged out                            |
+| `prompt_input_exit`           | User exited while prompt input was visible |
+| `bypass_permissions_disabled` | Bypass permissions mode was disabled       |
+| `other`                       | Other exit reasons                         |
+
+#### SessionEnd input
+
+In 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.
 
 ```json  theme={null}
 {
   "session_id": "abc123",
-  "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
-  "cwd": "/Users/...",
-  "permission_mode": "default",
-  "hook_event_name": "Setup",
-  "trigger": "init"
-}
-```
-
-The `trigger` field will be either `"init"` (from `--init` or `--init-only`) or `"maintenance"` (from `--maintenance`).
-
-### SessionStart Input
-
-```json  theme={null}
-{
-  "session_id": "abc123",
-  "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
-  "cwd": "/Users/...",
-  "permission_mode": "default",
-  "hook_event_name": "SessionStart",
-  "source": "startup",
-  "model": "claude-sonnet-4-20250514"
-}
-```
-
-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 when available. If you start Claude Code with `claude --agent <name>`, an `agent_type` field contains the agent name.
-
-### SubagentStart Input
-
-```json  theme={null}
-{
-  "session_id": "abc123",
-  "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
-  "cwd": "/Users/...",
-  "permission_mode": "default",
-  "hook_event_name": "SubagentStart",
-  "agent_id": "agent-abc123",
-  "agent_type": "Explore"
-}
-```
-
-Triggered 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).
-
-### SessionEnd Input
-
-```json  theme={null}
-{
-  "session_id": "abc123",
-  "transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
+  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
   "cwd": "/Users/...",
   "permission_mode": "default",
   "hook_event_name": "SessionEnd",
-  "reason": "exit"
-}
-```
-
-## Hook Output
-
-There are two mutually exclusive ways for hooks to return output back to Claude Code. The output
-communicates whether to block and any feedback that should be shown to Claude
-and the user.
-
-### Simple: Exit Code
-
-Hooks communicate status through exit codes, stdout, and stderr:
-
-* **Exit code 0**: Success. `stdout` is shown to the user in verbose mode
-  (ctrl+o), except for `UserPromptSubmit` and `SessionStart`, where stdout is
-  added to the context. JSON output in `stdout` is parsed for structured control
-  (see [Advanced: JSON Output](#advanced-json-output)).
-* **Exit code 2**: Blocking error. Only `stderr` is used as the error message
-  and fed back to Claude. The format is `[command]: {stderr}`. JSON in `stdout`
-  is **not** processed for exit code 2. See per-hook-event behavior below.
-* **Other exit codes**: Non-blocking error. `stderr` is shown to the user in verbose mode (ctrl+o) with
-  format `Failed with non-blocking status code: {stderr}`. If `stderr` is empty,
-  it shows `No stderr output`. Execution continues.
-
-<Warning>
-  Reminder: Claude Code does not see stdout if the exit code is 0, except for
-  the `UserPromptSubmit` hook where stdout is injected as context.
-</Warning>
-
-#### Exit Code 2 Behavior
-
-| Hook Event          | Behavior                                                           |
-| ------------------- | ------------------------------------------------------------------ |
-| `PreToolUse`        | Blocks the tool call, shows stderr to Claude                       |
-| `PermissionRequest` | Denies the permission, shows stderr to Claude                      |
-| `PostToolUse`       | Shows stderr to Claude (tool already ran)                          |
-| `Notification`      | N/A, shows stderr to user only                                     |
-| `UserPromptSubmit`  | Blocks prompt processing, erases prompt, shows stderr to user only |
-| `Stop`              | Blocks stoppage, shows stderr to Claude                            |
-| `SubagentStop`      | Blocks stoppage, shows stderr to Claude subagent                   |
-| `PreCompact`        | N/A, shows stderr to user only                                     |
-| `Setup`             | N/A, shows stderr to user only                                     |
-| `SessionStart`      | N/A, shows stderr to user only                                     |
-| `SessionEnd`        | N/A, shows stderr to user only                                     |
-
-### Advanced: JSON Output
-
-Hooks can return structured JSON in `stdout` for more sophisticated control.
-
-<Warning>
-  JSON output is only processed when the hook exits with code 0. If your hook
-  exits with code 2 (blocking error), `stderr` text is used directly—any JSON in `stdout`
-  is ignored. For other non-zero exit codes, only `stderr` is shown to the user in verbose mode (ctrl+o).
-</Warning>
-
-#### Common JSON Fields
-
-All hook types can include these optional fields:
-
-```json  theme={null}
-{
-  "continue": true, // Whether Claude should continue after hook execution (default: true)
-  "stopReason": "string", // Message shown when continue is false
-
-  "suppressOutput": true, // Hide stdout from transcript mode (default: false)
-  "systemMessage": "string" // Optional warning message shown to the user
-}
-```
-
-If `continue` is false, Claude stops processing after the hooks run.
-
-* For `PreToolUse`, this is different from `"permissionDecision": "deny"`, which
-  only blocks a specific tool call and provides automatic feedback to Claude.
-* For `PostToolUse`, this is different from `"decision": "block"`, which
-  provides automated feedback to Claude.
-* For `UserPromptSubmit`, this prevents the prompt from being processed.
-* For `Stop` and `SubagentStop`, this takes precedence over any
-  `"decision": "block"` output.
-* In all cases, `"continue" = false` takes precedence over any
-  `"decision": "block"` output.
-
-`stopReason` accompanies `continue` with a reason shown to the user, not shown
-to Claude.
-
-#### `PreToolUse` Decision Control
-
-`PreToolUse` hooks can control whether a tool call proceeds.
-
-* `"allow"` bypasses the permission system. `permissionDecisionReason` is shown
-  to the user but not to Claude.
-* `"deny"` prevents the tool call from executing. `permissionDecisionReason` is
-  shown to Claude.
-* `"ask"` asks the user to confirm the tool call in the UI.
-  `permissionDecisionReason` is shown to the user but not to Claude.
-
-Additionally, hooks can modify tool inputs before execution using `updatedInput`:
-
-* `updatedInput` modifies the tool's input parameters before the tool executes
-* Combine with `"permissionDecision": "allow"` to modify the input and auto-approve the tool call
-* Combine with `"permissionDecision": "ask"` to modify the input and show it to the user for confirmation
-
-Hooks can also provide context to Claude using `additionalContext`:
-
-* `"hookSpecificOutput.additionalContext"` adds a string to Claude's context before the tool executes.
-
-```json  theme={null}
-{
-  "hookSpecificOutput": {
-    "hookEventName": "PreToolUse",
-    "permissionDecision": "allow",
-    "permissionDecisionReason": "My reason here",
-    "updatedInput": {
-      "field_to_modify": "new value"
-    },
-    "additionalContext": "Current environment: production. Proceed with caution."
-  }
-}
-```
-
-<Note>
-  The `decision` and `reason` fields are deprecated for PreToolUse hooks.
-  Use `hookSpecificOutput.permissionDecision` and
-  `hookSpecificOutput.permissionDecisionReason` instead. The deprecated fields
-  `"approve"` and `"block"` map to `"allow"` and `"deny"` respectively.
-</Note>
-
-#### `PermissionRequest` Decision Control
-
-`PermissionRequest` hooks can allow or deny permission requests shown to the user.
-
-* For `"behavior": "allow"` you can also optionally pass in an `"updatedInput"` that modifies the tool's input parameters before the tool executes.
-* 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.
-
-```json  theme={null}
-{
-  "hookSpecificOutput": {
-    "hookEventName": "PermissionRequest",
-    "decision": {
-      "behavior": "allow",
-      "updatedInput": {
-        "command": "npm run lint"
-      }
-    }
-  }
-}
-```
-
-#### `PostToolUse` Decision Control
-
-`PostToolUse` hooks can provide feedback to Claude after tool execution.
-
-* `"block"` automatically prompts Claude with `reason`.
-* `undefined` does nothing. `reason` is ignored.
-* `"hookSpecificOutput.additionalContext"` adds context for Claude to consider.
-
-```json  theme={null}
-{
-  "decision": "block" | undefined,
-  "reason": "Explanation for decision",
-  "hookSpecificOutput": {
-    "hookEventName": "PostToolUse",
-    "additionalContext": "Additional information for Claude"
-  }
-}
-```
-
-#### `UserPromptSubmit` Decision Control
-
-`UserPromptSubmit` hooks can control whether a user prompt is processed and add context.
-
-**Adding context (exit code 0):**
-There are two ways to add context to the conversation:
-
-1. **Plain text stdout** (simpler): Any non-JSON text written to stdout is added
-   as context. This is the easiest way to inject information.
-
-2. **JSON with `additionalContext`** (structured): Use the JSON format below for
-   more control. The `additionalContext` field is added as context.
-
-Both methods work with exit code 0. Plain stdout is shown as hook output in
-the transcript; `additionalContext` is added more discretely.
-
-**Blocking prompts:**
-
-* `"decision": "block"` prevents the prompt from being processed. The submitted
-  prompt is erased from context. `"reason"` is shown to the user but not added
-  to context.
-* `"decision": undefined` (or omitted) allows the prompt to proceed normally.
-
-```json  theme={null}
-{
-  "decision": "block" | undefined,
-  "reason": "Explanation for decision",
-  "hookSpecificOutput": {
-    "hookEventName": "UserPromptSubmit",
-    "additionalContext": "My additional context here"
-  }
-}
-```
-
-<Note>
-  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
-  block prompts or want more structured control.
-</Note>
-
-#### `Stop`/`SubagentStop` Decision Control
-
-`Stop` and `SubagentStop` hooks can control whether Claude must continue.
-
-* `"block"` prevents Claude from stopping. You must populate `reason` for Claude
-  to know how to proceed.
-* `undefined` allows Claude to stop. `reason` is ignored.
-
-```json  theme={null}
-{
-  "decision": "block" | undefined,
-  "reason": "Must be provided when Claude is blocked from stopping"
-}
-```
-
-#### `Setup` Decision Control
-
-`Setup` hooks allow you to load context and configure the environment during repository initialization or maintenance.
-
-* `"hookSpecificOutput.additionalContext"` adds the string to the context.
-* Multiple hooks' `additionalContext` values are concatenated.
-* Setup hooks have access to `CLAUDE_ENV_FILE` for persisting environment variables.
-
-```json  theme={null}
-{
-  "hookSpecificOutput": {
-    "hookEventName": "Setup",
-    "additionalContext": "Repository initialized with custom configuration"
-  }
-}
-```
-
-#### `SessionStart` Decision Control
-
-`SessionStart` hooks allow you to load in context at the start of a session.
-
-* `"hookSpecificOutput.additionalContext"` adds the string to the context.
-* Multiple hooks' `additionalContext` values are concatenated.
-
-```json  theme={null}
-{
-  "hookSpecificOutput": {
-    "hookEventName": "SessionStart",
-    "additionalContext": "My additional context here"
-  }
-}
-```
-
-#### `SessionEnd` Decision Control
-
-`SessionEnd` hooks run when a session ends. They cannot block session termination
-but can perform cleanup tasks.
-
-#### Exit Code Example: Bash Command Validation
-
-```python  theme={null}
-#!/usr/bin/env python3
-import json
-import re
-import sys
-
-# Define validation rules as a list of (regex pattern, message) tuples
-VALIDATION_RULES = [
-    (
-        r"\bgrep\b(?!.*\|)",
-        "Use 'rg' (ripgrep) instead of 'grep' for better performance and features",
-    ),
-    (
-        r"\bfind\s+\S+\s+-name\b",
-        "Use 'rg --files | rg pattern' or 'rg --files -g pattern' instead of 'find -name' for better performance",
-    ),
-]
-
-
-def validate_command(command: str) -> list[str]:
-    issues = []
-    for pattern, message in VALIDATION_RULES:
-        if re.search(pattern, command):
-            issues.append(message)
-    return issues
-
-
-try:
-    input_data = json.load(sys.stdin)
-except json.JSONDecodeError as e:
-    print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
-    sys.exit(1)
-
-tool_name = input_data.get("tool_name", "")
-tool_input = input_data.get("tool_input", {})
-command = tool_input.get("command", "")
-
-if tool_name != "Bash" or not command:
-    sys.exit(1)
-
-# Validate the command
-issues = validate_command(command)
-
-if issues:
-    for message in issues:
-        print(f"• {message}", file=sys.stderr)
-    # Exit code 2 blocks tool call and shows stderr to Claude
-    sys.exit(2)
-```
-
-#### JSON Output Example: UserPromptSubmit to Add Context and Validation
-
-<Note>
-  For `UserPromptSubmit` hooks, you can inject context using either method:
-
-  * **Plain text stdout** with exit code 0: Simplest approach, prints text
-  * **JSON output** with exit code 0: Use `"decision": "block"` to reject prompts,
-    or `additionalContext` for structured context injection
-
-  Remember: Exit code 2 only uses `stderr` for the error message. To block using
-  JSON (with a custom reason), use `"decision": "block"` with exit code 0.
-</Note>
-
-```python  theme={null}
-#!/usr/bin/env python3
-import json
-import sys
-import re
-import datetime
-
-# Load input from stdin
-try:
-    input_data = json.load(sys.stdin)
-except json.JSONDecodeError as e:
-    print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
-    sys.exit(1)
-
-prompt = input_data.get("prompt", "")
-
-# Check for sensitive patterns
-sensitive_patterns = [
-    (r"(?i)\b(password|secret|key|token)\s*[:=]", "Prompt contains potential secrets"),
-]
-
-for pattern, message in sensitive_patterns:
-    if re.search(pattern, prompt):
-        # Use JSON output to block with a specific reason
-        output = {
-            "decision": "block",
-            "reason": f"Security policy violation: {message}. Please rephrase your request without sensitive information."
-        }
-        print(json.dumps(output))
-        sys.exit(0)
-
-# Add current time to context
-context = f"Current time: {datetime.datetime.now()}"
-print(context)
-
-"""
-The following is also equivalent:
-print(json.dumps({
-  "hookSpecificOutput": {
-    "hookEventName": "UserPromptSubmit",
-    "additionalContext": context,
-  },
-}))
-"""
-
-# Allow the prompt to proceed with the additional context
-sys.exit(0)
-```
-
-#### JSON Output Example: PreToolUse with Approval
-
-```python  theme={null}
-#!/usr/bin/env python3
-import json
-import sys
-
-# Load input from stdin
-try:
-    input_data = json.load(sys.stdin)
-except json.JSONDecodeError as e:
-    print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
-    sys.exit(1)
-
-tool_name = input_data.get("tool_name", "")
-tool_input = input_data.get("tool_input", {})
-
-# Example: Auto-approve file reads for documentation files
-if tool_name == "Read":
-    file_path = tool_input.get("file_path", "")
-    if file_path.endswith((".md", ".mdx", ".txt", ".json")):
-        # Use JSON output to auto-approve the tool call
-        output = {
-            "decision": "approve",
-            "reason": "Documentation file auto-approved",
-            "suppressOutput": True  # Don't show in verbose mode
-        }
-        print(json.dumps(output))
-        sys.exit(0)
-
-# For other cases, let the normal permission flow proceed
-sys.exit(0)
-```
-
-## Working with MCP Tools
-
-Claude Code hooks work seamlessly with
-[Model Context Protocol (MCP) tools](/en/mcp). When MCP servers
-provide tools, they appear with a special naming pattern that you can match in
-your hooks.
-
-### MCP Tool Naming
-
-MCP tools follow the pattern `mcp__<server>__<tool>`, for example:
-
-* `mcp__memory__create_entities` - Memory server's create entities tool
-* `mcp__filesystem__read_file` - Filesystem server's read file tool
-* `mcp__github__search_repositories` - GitHub server's search tool
-
-### Configuring Hooks for MCP Tools
-
-You can target specific MCP tools or entire MCP servers:
+  "reason": "other"
+}
+```
+
+SessionEnd hooks have no decision control. They cannot block session termination but can perform cleanup tasks.
+
+## Prompt-based hooks
+
+In addition to Bash command hooks (`type: "command"`), Claude Code supports prompt-based hooks (`type: "prompt"`) that use an LLM to evaluate whether to allow or block an action. Prompt-based hooks work with the following events: `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `UserPromptSubmit`, `Stop`, and `SubagentStop`.
+
+### How prompt-based hooks work
+
+Instead of executing a Bash command, prompt-based hooks:
+
+1. Send the hook input and your prompt to a Claude model, Haiku by default
+2. The LLM responds with structured JSON containing a decision
+3. Claude Code processes the decision automatically
+
+### Prompt hook configuration
+
+Set `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.
+
+This `Stop` hook asks the LLM to evaluate whether all tasks are complete before allowing Claude to finish:
 
 ```json  theme={null}
 {
   "hooks": {
-    "PreToolUse": [
+    "Stop": [
       {
-        "matcher": "mcp__memory__.*",
         "hooks": [
           {
-            "type": "command",
-            "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
-          }
-        ]
-      },
-      {
-        "matcher": "mcp__.*__write.*",
-        "hooks": [
-          {
-            "type": "command",
-            "command": "/home/user/scripts/validate-mcp-write.py"
+            "type": "prompt",
+            "prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete."
           }
         ]
       }
@@ -1261,102 +1171,219 @@
 }
 ```
 
-## Examples
-
-<Tip>
-  For practical examples including code formatting, notifications, and file protection, see [More Examples](/en/hooks-guide#more-examples) in the get started guide.
-</Tip>
-
-## Security Considerations
+| Field     | Required | Description                                                                                                                                                         |
+| :-------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `type`    | yes      | Must be `"prompt"`                                                                                                                                                  |
+| `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 |
+| `model`   | no       | Model to use for evaluation. Defaults to a fast model                                                                                                               |
+| `timeout` | no       | Timeout in seconds. Default: 30                                                                                                                                     |
+
+### Response schema
+
+The LLM must respond with JSON containing:
+
+```json  theme={null}
+{
+  "ok": true | false,
+  "reason": "Explanation for the decision"
+}
+```
+
+| Field    | Description                                                |
+| :------- | :--------------------------------------------------------- |
+| `ok`     | `true` allows the action, `false` prevents it              |
+| `reason` | Required when `ok` is `false`. Explanation shown to Claude |
+
+### Example: Multi-criteria Stop hook
+
+This `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:
+
+```json  theme={null}
+{
+  "hooks": {
+    "Stop": [
+      {
+        "hooks": [
+          {
+            "type": "prompt",
+            "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.",
+            "timeout": 30
+          }
+        ]
+      }
+    ]
+  }
+}
+```
+
+## Agent-based hooks
+
+Agent-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.
+
+### How agent hooks work
+
+When an agent hook fires:
+
+1. Claude Code spawns a subagent with your prompt and the hook's JSON input
+2. The subagent can use tools like Read, Grep, and Glob to investigate
+3. After up to 50 turns, the subagent returns a structured `{ "ok": true/false }` decision
+4. Claude Code processes the decision the same way as a prompt hook
+
+Agent hooks are useful when verification requires inspecting actual files or test output, not just evaluating the hook input data alone.
+
+### Agent hook configuration
+
+Set `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:
+
+| Field     | Required | Description                                                                                 |
+| :-------- | :------- | :------------------------------------------------------------------------------------------ |
+| `type`    | yes      | Must be `"agent"`                                                                           |
+| `prompt`  | yes      | Prompt describing what to verify. Use `$ARGUMENTS` as a placeholder for the hook input JSON |
+| `model`   | no       | Model to use. Defaults to a fast model                                                      |
+| `timeout` | no       | Timeout in seconds. Default: 60                                                             |
+
+The response schema is the same as prompt hooks: `{ "ok": true }` to allow or `{ "ok": false, "reason": "..." }` to block.
+
+This `Stop` hook verifies that all unit tests pass before allowing Claude to finish:
+
+```json  theme={null}
+{
+  "hooks": {
+    "Stop": [
+      {
+        "hooks": [
+          {
+            "type": "agent",
+            "prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
+            "timeout": 120
+          }
+        ]
+      }
+    ]
+  }
+}
+```
+
+## Run hooks in the background
+
+By 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.
+
+### Configure an async hook
+
+Add `"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.
+
+This 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:
+
+```json  theme={null}
+{
+  "hooks": {
+    "PostToolUse": [
+      {
+        "matcher": "Write",
+        "hooks": [
+          {
+            "type": "command",
+            "command": "/path/to/run-tests.sh",
+            "async": true,
+            "timeout": 120
+          }
+        ]
+      }
+    ]
+  }
+}
+```
+
+The `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.
+
+### How async hooks execute
+
+When 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.
+
+After 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.
+
+### Example: run tests after file changes
+
+This 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`:
+
+```bash  theme={null}
+#!/bin/bash
+# run-tests-async.sh
+
+# Read hook input from stdin
+INPUT=$(cat)
+FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
+
+# Only run tests for source files
+if [[ "$FILE_PATH" != *.ts && "$FILE_PATH" != *.js ]]; then
+  exit 0
+fi
+
+# Run tests and report results via systemMessage
+RESULT=$(npm test 2>&1)
+EXIT_CODE=$?
+
+if [ $EXIT_CODE -eq 0 ]; then
+  echo "{\"systemMessage\": \"Tests passed after editing $FILE_PATH\"}"
+else
+  echo "{\"systemMessage\": \"Tests failed after editing $FILE_PATH: $RESULT\"}"
+fi
+```
+
+Then add this configuration to `.claude/settings.json` in your project root. The `async: true` flag lets Claude keep working while tests run:
+
+```json  theme={null}
+{
+  "hooks": {
+    "PostToolUse": [
+      {
+        "matcher": "Write|Edit",
+        "hooks": [
+          {
+            "type": "command",
+            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/run-tests-async.sh",
+            "async": true,
+            "timeout": 300
+          }
+        ]
+      }
+    ]
+  }
+}
+```
+
+### Limitations
+
+Async hooks have several constraints compared to synchronous hooks:
+
+* Only `type: "command"` hooks support `async`. Prompt-based hooks cannot run asynchronously.
+* Async hooks cannot block tool calls or return decisions. By the time the hook completes, the triggering action has already proceeded.
+* Hook output is delivered on the next conversation turn. If the session is idle, the response waits until the next user interaction.
+* Each execution creates a separate background process. There is no deduplication across multiple firings of the same async hook.
+
+## Security considerations
 
 ### Disclaimer
 
-**USE AT YOUR OWN RISK**: Claude Code hooks execute arbitrary shell commands on
-your system automatically. By using hooks, you acknowledge that:
-
-* You are solely responsible for the commands you configure
-* Hooks can modify, delete, or access any files your user account can access
-* Malicious or poorly written hooks can cause data loss or system damage
-* Anthropic provides no warranty and assumes no liability for any damages
-  resulting from hook usage
-* You should thoroughly test hooks in a safe environment before production use
-
-Always review and understand any hook commands before adding them to your
-configuration.
-
-### Security Best Practices
-
-Here are some key practices for writing more secure hooks:
-
-1. **Validate and sanitize inputs** - Never trust input data blindly
-2. **Always quote shell variables** - Use `"$VAR"` not `$VAR`
-3. **Block path traversal** - Check for `..` in file paths
-4. **Use absolute paths** - Specify full paths for scripts (use
-   "\$CLAUDE\_PROJECT\_DIR" for the project path)
-5. **Skip sensitive files** - Avoid `.env`, `.git/`, keys, etc.
-
-### Configuration Safety
-
-Direct edits to hooks in settings files don't take effect immediately. Claude
-Code:
-
-1. Captures a snapshot of hooks at startup
-2. Uses this snapshot throughout the session
-3. Warns if hooks are modified externally
-4. Requires review in `/hooks` menu for changes to apply
-
-This prevents malicious hook modifications from affecting your current session.
-
-## Hook Execution Details
-
-* **Timeout**: 60-second execution limit by default, configurable per command.
-  * A timeout for an individual command does not affect the other commands.
-* **Parallelization**: All matching hooks run in parallel
-* **Deduplication**: Multiple identical hook commands are deduplicated automatically
-* **Environment**: Runs in current directory with Claude Code's environment
-  * The `CLAUDE_PROJECT_DIR` environment variable is available and contains the
-    absolute path to the project root directory (where Claude Code was started)
-  * 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.
-* **Input**: JSON via stdin
-* **Output**:
-  * PreToolUse/PermissionRequest/PostToolUse/Stop/SubagentStop: Progress shown in verbose mode (ctrl+o)
-  * Notification/SessionEnd: Logged to debug only (`--debug`)
-  * UserPromptSubmit/SessionStart/Setup: stdout added as context for Claude
-
-## Debugging
-
-### Basic Troubleshooting
-
-If your hooks aren't working:
-
-1. **Check configuration** - Run `/hooks` to see if your hook is registered
-2. **Verify syntax** - Ensure your JSON settings are valid
-3. **Test commands** - Run hook commands manually first
-4. **Check permissions** - Make sure scripts are executable
-5. **Review logs** - Use `claude --debug` to see hook execution details
-
-Common issues:
-
-* **Quotes not escaped** - Use `\"` inside JSON strings
-* **Wrong matcher** - Check tool names match exactly (case-sensitive)
-* **Command not found** - Use full paths for scripts
-
-### Advanced Debugging
-
-For complex hook issues:
-
-1. **Inspect hook execution** - Use `claude --debug` to see detailed hook
-   execution
-2. **Validate JSON schemas** - Test hook input/output with external tools
-3. **Check environment variables** - Verify Claude Code's environment is correct
-4. **Test edge cases** - Try hooks with unusual file paths or inputs
-5. **Monitor system resources** - Check for resource exhaustion during hook
-   execution
-6. **Use structured logging** - Implement logging in your hook scripts
-
-### Debug Output Example
-
-Use `claude --debug` to see hook execution details:
+Hooks run with your system user's full permissions.
+
+<Warning>
+  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.
+</Warning>
+
+### Security best practices
+
+Keep these practices in mind when writing hooks:
+
+* **Validate and sanitize inputs**: never trust input data blindly
+* **Always quote shell variables**: use `"$VAR"` not `$VAR`
+* **Block path traversal**: check for `..` in file paths
+* **Use absolute paths**: specify full paths for scripts, using `"$CLAUDE_PROJECT_DIR"` for the project root
+* **Skip sensitive files**: avoid `.env`, `.git/`, keys, etc.
+
+## Debug hooks
+
+Run `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.
 
 ```
 [DEBUG] Executing hooks for PostToolUse:Write
@@ -1364,13 +1391,8 @@
 [DEBUG] Found 1 hook matchers in settings
 [DEBUG] Matched 1 hooks for query "Write"
 [DEBUG] Found 1 hook commands to execute
-[DEBUG] Executing hook command: <Your command> with timeout 60000ms
+[DEBUG] Executing hook command: <Your command> with timeout 600000ms
 [DEBUG] Hook command completed with status 0: <Your stdout>
 ```
 
-Progress messages appear in verbose mode (ctrl+o) showing:
-
-* Which hook is running
-* Command being executed
-* Success/failure status
-* Output or error messages
+For 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.