# Claude Code Hooks Reference

Source: https://code.claude.com/docs/en/hooks

## Hook Event Types (27 Total)

| Event | When Fires | Can Block? |
|-------|-----------|-----------|
| SessionStart | New/resume session | No |
| UserPromptSubmit | User submits prompt | Yes |
| PreToolUse | Before tool executes | Yes |
| PermissionRequest | Permission dialog appears | Yes |
| PermissionDenied | Auto mode denies tool | No |
| PostToolUse | After tool succeeds | No |
| PostToolUseFailure | After tool fails | No |
| Notification | Notification sent | No |
| SubagentStart | Subagent spawned | No |
| SubagentStop | Subagent finishes | Yes |
| TaskCreated | Task created | Yes |
| TaskCompleted | Task marked complete | Yes |
| Stop | Claude finishes responding | Yes |
| StopFailure | Turn ends with API error | No |
| TeammateIdle | Agent team teammate idle | Yes |
| InstructionsLoaded | CLAUDE.md/.claude/rules/*.md loaded | No |
| ConfigChange | Config file changes | Yes |
| CwdChanged | Working directory changes | No |
| FileChanged | Watched file changes | No |
| WorktreeCreate | Worktree created | Yes |
| WorktreeRemove | Worktree removed | No |
| PreCompact | Before context compaction | No |
| PostCompact | After context compaction | No |
| Elicitation | MCP server requests input | Yes |
| ElicitationResult | User responds to MCP elicitation | Yes |
| SessionEnd | Session terminates | No |

## Hook Configuration Format

Three-level JSON nesting: event -> matcher -> handler array.

```json
{
  "hooks": {
    "EventName": [
      {
        "matcher": "ToolName|OtherTool|pattern",
        "hooks": [
          {
            "type": "command|http|prompt|agent",
            "if": "Permission rule syntax (optional)",
            "command": "/path/to/script.sh",
            "url": "http://localhost:8080/hooks",
            "prompt": "Evaluation prompt",
            "timeout": 600,
            "statusMessage": "Custom message",
            "once": false
          }
        ]
      }
    ]
  },
  "disableAllHooks": false
}
```

### Hook Locations & Scope (merge order, higher priority wins)

1. User settings: `~/.claude/settings.json` (not shareable)
2. Project settings: `.claude/settings.json` (shareable)
3. Local settings: `.claude/settings.local.json` (not shareable)
4. Managed policy settings (organization-wide, shareable)
5. Plugins: `hooks/hooks.json` (shareable)
6. Skills/agents: frontmatter (shareable)

## Hook Handler Types

### Command Hooks (`type: "command"`)

Execute shell commands. Input on stdin as JSON, results via exit codes and stdout.

```json
{
  "type": "command",
  "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/my-script.sh",
  "async": false,
  "shell": "bash"
}
```

**Exit codes:**
- **0**: Success -- stdout parsed for JSON output
- **2**: Blocking error -- stderr is error message, action blocked
- **Other**: Non-blocking error -- shown in verbose mode only

### HTTP Hooks (`type: "http"`)

```json
{
  "type": "http",
  "url": "http://localhost:8080/hooks/pre-tool-use",
  "timeout": 30,
  "headers": { "Authorization": "Bearer $MY_TOKEN" },
  "allowedEnvVars": ["MY_TOKEN"]
}
```

### Prompt Hooks (`type: "prompt"`)

Single-turn Claude evaluation.

```json
{
  "type": "prompt",
  "prompt": "Is this command safe? $ARGUMENTS",
  "model": "fast-model",
  "timeout": 30
}
```

### Agent Hooks (`type: "agent"`)

Spawn subagent to verify before decision.

```json
{
  "type": "agent",
  "prompt": "Verify this meets requirements: $ARGUMENTS",
  "timeout": 60
}
```

## Common Input Fields (All Events)

Every hook receives JSON on stdin (command) or POST body (HTTP):

```json
{
  "session_id": "abc123",
  "transcript_path": "/path/to/transcript.jsonl",
  "cwd": "/current/working/dir",
  "permission_mode": "default|plan|acceptEdits|auto|dontAsk|bypassPermissions",
  "hook_event_name": "PreToolUse",
  "agent_id": "agent-xyz (if in subagent)",
  "agent_type": "Explore (if in subagent)"
}
```

## Event-Specific Input/Output Schemas

### SessionStart

**Input:**
```json
{
  "source": "startup|resume|clear|compact",
  "model": "claude-sonnet-4-6"
}
```

**Output:**
```json
{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "Context to add for Claude"
  }
}
```

Special: Access `$CLAUDE_ENV_FILE` to persist env vars for the session.

### UserPromptSubmit

**Input:**
```json
{
  "prompt": "User's prompt text"
}
```

**Output:**
```json
{
  "decision": "block",
  "reason": "Why blocked",
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": "Additional context"
  }
}
```

### PreToolUse

**Matcher:** Tool name (Bash, Edit, Write, Read, Glob, Grep, Agent, WebFetch, WebSearch, AskUserQuestion, ExitPlanMode, mcp__*)

**Input:**
```json
{
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test",
    "description": "...",
    "timeout": 600,
    "run_in_background": false
  },
  "tool_use_id": "toolu_01ABC123"
}
```

**Output:**
```json
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow|deny|ask|defer",
    "permissionDecisionReason": "Reason shown to user",
    "updatedInput": {
      "command": "modified command"
    },
    "additionalContext": "Context for Claude"
  }
}
```

**Permission decision precedence (multiple hooks):** `deny` > `defer` > `ask` > `allow`

### PermissionRequest

**Matcher:** Tool name

**Input:**
```json
{
  "tool_name": "Bash",
  "tool_input": { "command": "rm -rf node_modules" },
  "permission_suggestions": [
    {
      "type": "addRules",
      "rules": [{ "toolName": "Bash", "ruleContent": "rm -rf node_modules" }],
      "behavior": "allow",
      "destination": "localSettings"
    }
  ]
}
```

**Output:**
```json
{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow|deny",
      "updatedInput": { "command": "modified" },
      "updatedPermissions": [
        {
          "type": "addRules|replaceRules|removeRules|setMode|addDirectories|removeDirectories",
          "rules": [],
          "behavior": "allow|deny|ask",
          "destination": "session|localSettings|projectSettings|userSettings"
        }
      ],
      "message": "Why denied"
    }
  }
}
```

### PostToolUse

**Matcher:** Tool name

**Input:**
```json
{
  "tool_name": "Write",
  "tool_input": { "file_path": "/path/to/file.txt", "content": "..." },
  "tool_response": { "filePath": "/path/to/file.txt", "success": true },
  "tool_use_id": "toolu_01ABC123"
}
```

**Output:**
```json
{
  "decision": "block",
  "reason": "Reason for block",
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "Context for Claude",
    "updatedMCPToolOutput": "For MCP tools only: replace output"
  }
}
```

### PostToolUseFailure

**Matcher:** Tool name

**Input:**
```json
{
  "tool_name": "Bash",
  "tool_input": { "command": "npm test" },
  "tool_use_id": "toolu_01ABC123",
  "error": "Command exited with non-zero status code 1",
  "is_interrupt": false
}
```

### PermissionDenied

**Matcher:** Tool name

**Input:**
```json
{
  "tool_name": "Bash",
  "tool_input": { "command": "rm -rf /tmp/build" },
  "tool_use_id": "toolu_01ABC123",
  "reason": "Auto mode denied: command targets path outside project"
}
```

**Output:**
```json
{
  "hookSpecificOutput": {
    "hookEventName": "PermissionDenied",
    "retry": true
  }
}
```

### Notification

**Matcher:** `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog`

**Input:**
```json
{
  "message": "Claude needs your permission to use Bash",
  "title": "Permission needed",
  "notification_type": "permission_prompt"
}
```

### SubagentStart

**Matcher:** Agent type (Bash, Explore, Plan, or custom agent names)

**Input:**
```json
{
  "agent_id": "agent-abc123",
  "agent_type": "Explore"
}
```

**Output:**
```json
{
  "hookSpecificOutput": {
    "hookEventName": "SubagentStart",
    "additionalContext": "Context for subagent"
  }
}
```

**Critical context note:** SubagentStart fires in the **parent agent's** hook context. The subagent does not yet exist. This is the correct place to act on the parent's behalf (e.g., set state on the parent's resources).

### SubagentStop

**Matcher:** Agent type

**Input:**
```json
{
  "stop_hook_active": false,
  "agent_id": "agent-abc123",
  "agent_type": "Explore",
  "agent_transcript_path": "~/.claude/projects/.../subagents/agent-abc123.jsonl",
  "last_assistant_message": "Analysis complete..."
}
```

**Context note:** SubagentStop fires in the **parent agent's** hook context after the subagent has completed.

### Stop

No tool-specific input fields.

**Output:**
```json
{
  "decision": "block",
  "reason": "Reason to block completion"
}
```

### StopFailure

**Matcher:** Error type (rate_limit, authentication_failed, billing_error, invalid_request, server_error, max_output_tokens, unknown)

**Input:**
```json
{
  "error_type": "rate_limit"
}
```

### TeammateIdle

**Output:**
```json
{
  "continue": false,
  "stopReason": "Reason teammate stopped",
  "decision": "block"
}
```

### TaskCreated / TaskCompleted

**Input:**
```json
{
  "task_id": "task-001",
  "task_subject": "Implement feature",
  "task_description": "Description",
  "teammate_name": "implementer",
  "team_name": "my-project"
}
```

### InstructionsLoaded

**Matcher:** Load reason (session_start, nested_traversal, path_glob_match, include, compact)

**Input:**
```json
{
  "file_path": "/path/to/CLAUDE.md",
  "memory_type": "Project|User|Local|Managed",
  "load_reason": "session_start",
  "globs": ["pattern1", "pattern2"],
  "trigger_file_path": "/path/that/triggered/load",
  "parent_file_path": "/parent/CLAUDE.md"
}
```

### ConfigChange

**Matcher:** Config source (user_settings, project_settings, local_settings, policy_settings, skills)

### CwdChanged

No tool-specific input. Supports `$CLAUDE_ENV_FILE`.

### FileChanged

**Matcher:** Filename (basename of file that changed)

**Input:**
```json
{
  "file_path": "/path/to/file"
}
```

Supports `$CLAUDE_ENV_FILE`.

### WorktreeCreate

**Output (command hook):** Print worktree path to stdout.

**Output (HTTP hook):**
```json
{
  "hookSpecificOutput": {
    "worktreePath": "/path/to/worktree"
  }
}
```

Hooks **replace** git's default worktree behavior. Can block with non-zero exit code.

### WorktreeRemove

Failures logged in debug mode only.

### PreCompact / PostCompact

**Matcher:** Trigger (manual, auto). Informational only.

### Elicitation

**Matcher:** MCP server name

**Input:**
```json
{
  "tool_name": "mcp__memory__create_entities",
  "fields": [{ "name": "field", "type": "string", "description": "..." }]
}
```

**Output:**
```json
{
  "hookSpecificOutput": {
    "hookEventName": "Elicitation",
    "action": "accept|decline|cancel",
    "content": { "field": "value" }
  }
}
```

### ElicitationResult

**Matcher:** MCP server name

**Input:**
```json
{
  "tool_name": "mcp__memory__create_entities",
  "action": "accept",
  "content": { "field": "value" }
}
```

### SessionEnd

**Matcher:** Why session ended (clear, resume, logout, prompt_input_exit, bypass_permissions_disabled, other). Informational only.

## Common Output Fields (All Events)

```json
{
  "continue": true,
  "stopReason": "Message when continue: false",
  "suppressOutput": false,
  "systemMessage": "Warning shown to user",
  "additionalContext": "Added to Claude's context",
  "hookSpecificOutput": {
    "hookEventName": "EventName"
  }
}
```

## Matcher Patterns & Filtering

| Event | Matches on | Example |
|-------|-----------|---------|
| Tool events | tool name | `Bash`, `Edit\|Write`, `mcp__.*` |
| SessionStart/End | session source/reason | `startup`, `resume`, `clear` |
| Notification | notification type | `permission_prompt`, `idle_prompt` |
| SubagentStart/Stop | agent type | `Bash`, `Explore`, custom names |
| PreCompact/PostCompact | trigger | `manual`, `auto` |
| ConfigChange | config source | `user_settings`, `policy_settings` |
| FileChanged | filename | `.envrc`, `.env` |
| StopFailure | error type | `rate_limit`, `server_error` |
| InstructionsLoaded | load reason | `session_start`, `path_glob_match` |
| Elicitation/Result | MCP server | configured server names |
| CwdChanged, UserPromptSubmit | N/A | Always fires |

Omit matcher or use `"*"` to match all.

**Conditional filtering with `if`:**
```json
{
  "matcher": "Bash",
  "hooks": [
    { "if": "Bash(rm *)", "command": "block-rm.sh" },
    { "if": "Bash(git *)", "command": "log-git.sh" }
  ]
}
```

## Exit Code Behavior

| Code | Meaning | JSON Processed? |
|------|---------|-----------------|
| 0 | Success | Yes |
| 2 | Blocking error | No, stderr becomes error message |
| Other | Non-blocking error | No, shown in verbose mode only |

### Exit Code 2 Blocking by Event

| Event | Blocks? | Effect |
|-------|---------|--------|
| PreToolUse | Yes | Tool prevented |
| PermissionRequest | Yes | Permission denied |
| UserPromptSubmit | Yes | Prompt blocked |
| Stop | Yes | Prevents Claude stopping |
| SubagentStop | Yes | Subagent continues |
| TeammateIdle | Yes | Teammate continues |
| TaskCreated/Completed | Yes | Task not created/completed |
| ConfigChange | Yes | Change rejected |
| WorktreeCreate | Yes | Creation fails |
| All others | No | Shown to user only |

## Environment Variables Available in Hooks

- `$CLAUDE_PROJECT_DIR` -- Project root
- `$CLAUDE_PLUGIN_ROOT` -- Plugin directory
- `$CLAUDE_PLUGIN_DATA` -- Plugin data directory
- `$CLAUDE_ENV_FILE` -- File to persist env vars (SessionStart, CwdChanged, FileChanged only)
- `$CLAUDE_CODE_REMOTE` -- Set to "true" in remote web environments

## Key Caveats

1. **JSON output cap**: 10,000 characters -- larger output saved to file
2. **Deferred tools require `-p` flag**: PreToolUse returning `"defer"` only works in non-interactive mode
3. **MCP tools naming**: Follow pattern `mcp__<server>__<tool>`
4. **No individual hook disable**: Set `disableAllHooks: true` to disable all
5. **Parallel execution**: All matching hooks run in parallel; identical handlers deduplicated
6. **Timeout defaults**: Command 600s, prompt 30s, agent 60s
7. **HTTP non-2xx**: Connection failures/timeouts are non-blocking
8. **Worktree hooks replace default**: WorktreeCreate hooks replace git's default behavior
9. **SubagentStart fires on parent context**: The subagent does not yet exist when this fires
10. **SubagentStop fires on parent context**: The subagent has already completed
11. **PreToolUse fires on the calling agent's context**: When a subagent makes tool calls, PreToolUse fires in the subagent's context, NOT the parent's. The parent does not fire PreToolUse while waiting for a subagent.
12. **Notification|idle_prompt fires when agent becomes idle**: Use this to clean up state that should be cleared when the agent is no longer actively tooling.
