Skip to content
Back to skills

2635 Anthropic Hooks Reference 5f3eda56

ASecurity

> Part of the [Anthropic Best Practices](./anthropic-best-practices.md) series. > Covers: All 14 hook event types, input/output schemas, configuration, and design patterns. > > Sources: > - Claude Code Hooks Reference (`code.claude.com/docs/en/hooks`) > - Claude Code Hooks Guide (`code.claude.com/docs/en/hooks-guide`) ---

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
ai-agentspythongoshellbashgitdatabase

Works with

  • claude code
  • cli
  • mcp

Security analysis

A100/100

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 2635-anthropic-hooks-reference_5f3eda56 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 2635 Anthropic Hooks Reference 5f3eda56?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 2635 Anthropic Hooks Reference 5f3eda56
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-2635-anthropic-hooks-reference-5f3eda56/badge)](https://www.skillsdirectory.com/skills/tools-only-2635-anthropic-hooks-reference-5f3eda56)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
# Anthropic Hooks Reference

> Part of the [Anthropic Best Practices](./anthropic-best-practices.md) series.
> Covers: All 14 hook event types, input/output schemas, configuration, and design patterns.
>
> Sources:
> - Claude Code Hooks Reference (`code.claude.com/docs/en/hooks`)
> - Claude Code Hooks Guide (`code.claude.com/docs/en/hooks-guide`)

---

## 25. Hooks -- Lifecycle Events & Context Injection

Hooks are shell commands (or LLM prompts) that fire at specific lifecycle events. They let you observe, block, modify, or inject context into Claude's behavior -- without changing the model itself.

### Architecture

```
  User prompt -> [UserPromptSubmit] -> Claude thinks -> [PreToolUse] -> Permission check
       |                                                  |
  [SessionStart]                                   [PermissionRequest]
                                                          |
                                               Tool executes (or blocked)
                                                          |
                                                   [PostToolUse] or [PostToolUseFailure]
                                                          |
                                               Claude responds -> [Stop]
                                                          |
                                               [Notification] (if idle/permission)
                                                          |
                                               [SessionEnd] (on exit)
```

Subagent lifecycle: `[SubagentStart]` -> subagent works -> `[SubagentStop]`
Team lifecycle: teammate works -> `[TeammateIdle]` / `[TaskCompleted]`
Context management: `[PreCompact]` -> compaction occurs

### Hook Handler Types

Three types of handler can be attached to any event:

| Type | What It Does | Best For |
|------|-------------|----------|
| `command` | Runs a shell command, reads stdin JSON, writes stdout JSON | Scripts, telemetry, file operations |
| `prompt` | Sends input to an LLM for a single response | Quick analysis, classification |
| `agent` | Sends input to an agentic LLM that can use tools | Complex validation, multi-step checks |

```json
{
  "type": "command",
  "command": "python hooks/my_hook.py",
  "async": false,
  "timeout": 600,
  "statusMessage": "Running quality check...",
  "once": false
}
```

| Handler Field | Type | Description |
|---------------|------|-------------|
| `type` | `"command" \| "prompt" \| "agent"` | Required. Handler type |
| `command` | `string` | Required for `command` type. Shell command to run |
| `prompt` | `string` | Required for `prompt`/`agent` types. Use `$ARGUMENTS` for input |
| `model` | `string?` | For `prompt`/`agent`. Model to use (default: inherit) |
| `async` | `boolean?` | `command` only. Run in background without blocking |
| `timeout` | `number?` | Seconds before timeout (default varies by type) |
| `statusMessage` | `string?` | Custom spinner text while hook runs |
| `once` | `boolean?` | Run only once per session |

### Exit Code Protocol

| Exit Code | Meaning | Effect |
|-----------|---------|--------|
| `0` | Success / Allow | JSON on stdout parsed for decisions |
| `2` | Block | stderr message fed back to Claude or shown to user |
| Other | Non-blocking error | stderr shown in verbose mode only |

### Common Input Fields (All Events)

Every hook receives these on stdin as JSON:

| Field | Type | Description |
|-------|------|-------------|
| `session_id` | `string` | Current session identifier |
| `transcript_path` | `string` | Path to conversation transcript JSON |
| `cwd` | `string` | Current working directory |
| `permission_mode` | `string` | `default`, `plan`, `acceptEdits`, `dontAsk`, `bypassPermissions` |
| `hook_event_name` | `string` | Name of the event that fired |

### Universal Output Fields (All Events)

Any hook can return these in the stdout JSON:

| Field | Type | Description |
|-------|------|-------------|
| `continue` | `boolean?` | `false` = Claude stops the entire session |
| `stopReason` | `string?` | Message shown when `continue=false` |
| `suppressOutput` | `boolean?` | Hide stdout from verbose mode |
| `systemMessage` | `string?` | Warning message surfaced to the user |

---

### Event Reference

#### 1. SessionStart

**When:** Session begins, resumes, clears, or compacts.
**Matcher values:** `startup`, `resume`, `clear`, `compact`
**Can block:** No

**Input fields:**

| Field | Type | Description |
|-------|------|-------------|
| `source` | `"startup" \| "resume" \| "clear" \| "compact"` | What triggered the session |
| `model` | `string` | Active model identifier |
| `agent_type` | `string?` | Present if started with `--agent` flag |

**Output actions:**

| Action | How | Effect |
|--------|-----|--------|
| Inject context | `hookSpecificOutput.additionalContext` | Text added to Claude's system context |
| Set env vars | Write `export VAR=value` to `$CLAUDE_ENV_FILE` | Persisted for subsequent Bash tool calls |

```json
{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "Project is on sprint 14. Focus area: payments."
  }
}
```

---

#### 2. UserPromptSubmit

**When:** User submits a prompt, before Claude processes it.
**Matcher:** None
**Can block:** Yes (exit 2, or `decision: "block"` in output)

**Input fields:**

| Field | Type | Description |
|-------|------|-------------|
| `prompt` | `string` | The full text the user submitted |

**Output actions:**

| Action | How | Effect |
|--------|-----|--------|
| Block prompt | `"decision": "block"` + `"reason"` | Prompt rejected, reason shown to user |
| Inject context | `hookSpecificOutput.additionalContext` | Text added to Claude's context alongside the prompt |

```json
{
  "decision": "block",
  "reason": "Prompt contains production database credentials. Remove them before submitting.",
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": "User's last prompt was blocked for containing secrets."
  }
}
```

**Use cases:** Secret scanning, prompt transformation, injecting project state.

---

#### 3. PreToolUse

**When:** Before a tool executes (before permission check).
**Matcher:** Tool name regex -- `Bash`, `Edit`, `Write`, `Read`, `Glob`, `Grep`, `Task`, `WebFetch`, `WebSearch`, `mcp__server__tool`
**Can block:** Yes (via `permissionDecision: "deny"`)

**Input fields:**

| Field | Type | Description |
|-------|------|-------------|
| `tool_name` | `string` | Tool being called |
| `tool_input` | `object` | Tool-specific arguments (see below) |
| `tool_use_id` | `string` | Unique ID for this tool invocation |

**Tool-specific `tool_input` shapes:**

| Tool | Key Fields |
|------|-----------|
| **Bash** | `command`, `description?`, `timeout?`, `run_in_background?` |
| **Write** | `file_path`, `content` |
| **Edit** | `file_path`, `old_string`, `new_string`, `replace_all?` |
| **Read** | `file_path`, `offset?`, `limit?` |
| **Glob** | `pattern`, `path?` |
| **Grep** | `pattern`, `path?`, `glob?`, `output_mode?`, `-i?`, `multiline?` |
| **WebFetch** | `url`, `prompt` |
| **WebSearch** | `query`, `allowed_domains?`, `blocked_domains?` |
| **Task** | `prompt`, `description`, `subagent_type`, `model?` |
| **MCP tools** | Varies by MCP server/tool definition |

**Output actions:**

| Action | How | Effect |
|--------|-----|--------|
| Auto-allow | `permissionDecision: "allow"` | Bypasses permission prompt |
| Auto-deny | `permissionDecision: "deny"` | Blocks tool call, reason fed back to Claude |
| Force prompt | `permissionDecision: "ask"` | Shows permission dialog regardless of settings |
| Modify args | `updatedInput: { ... }` | Replaces tool_input before execution |
| Inject context | `additionalContext` | Text added to Claude's context |

```json
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Cannot write to production config files.",
    "updatedInput": null,
    "additionalContext": "Tool call was blocked by project policy."
  }
}
```

**Use cases:** Auto-approve safe operations, block dangerous commands, rewrite file paths, inject warnings.

---

#### 4. PermissionRequest

**When:** A permission dialog is about to be shown to the user.
**Matcher:** Tool name regex (same as PreToolUse)
**Can block:** Yes (via `decision.behavior: "deny"`)

**Input fields:**

| Field | Type | Description |
|-------|------|-------------|
| `tool_name` | `string` | Tool requesting permission |
| `tool_input` | `object` | Same tool-specific shapes as PreToolUse |
| `permission_suggestions` | `array?` | Suggested "always allow" rules |

**Output actions:**

| Action | How | Effect |
|--------|-----|--------|
| Auto-allow | `decision.behavior: "allow"` | Skips the permission dialog |
| Auto-deny | `decision.behavior: "deny"` + `decision.message` | Denies without prompting |
| Modify args | `decision.updatedInput: { ... }` | Changes tool args (allow only) |
| Update rules | `decision.updatedPermissions: { ... }` | Adjusts permission rules (allow only) |
| Interrupt | `interrupt: true` | Stops Claude entirely (deny only) |

```json
{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow",
      "updatedInput": { "command": "npm test -- --bail" },
      "updatedPermissions": {}
    },
    "interrupt": false
  }
}
```

**Use cases:** Auto-approve CI-safe commands, enforce org policies, add safety flags to commands.

---

#### 5. PostToolUse

**When:** After a tool executes successfully.
**Matcher:** Tool name regex
**Can block:** Yes (via `decision: "block"` -- doesn't undo the tool, but tells Claude to address the issue)

**Input fields:**

| Field | Type | Description |
|-------|------|-------------|
| `tool_name` | `string` | Tool that ran |
| `tool_input` | `object` | Args it was called with |
| `tool_response` | `object` | The tool's output/result |
| `tool_use_id` | `string` | Unique ID |

**Output actions:**

| Action | How | Effect |
|--------|-----|--------|
| Flag issue | `"decision": "block"` + `"reason"` | Claude sees the reason and must address it |
| Inject context | `additionalContext` | Added to Claude's context after the tool result |
| Replace MCP output | `updatedMCPToolOutput: { ... }` | For MCP tools only -- replaces tool response |

```json
{
  "decision": "block",
  "reason": "File was written without 'use server' directive. Add it before proceeding.",
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "Lint check failed on the written file.",
    "updatedMCPToolOutput": null
  }
}
```

**Use cases:** Post-write linting, enforcing coding standards, augmenting tool output, telemetry.

---

#### 6. PostToolUseFailure

**When:** After a tool fails.
**Matcher:** Tool name regex
**Can block:** No

**Input fields:**

| Field | Type | Description |
|-------|------|-------------|
| `tool_name` | `string` | Tool that failed |
| `tool_input` | `object` | Args it was called with |
| `tool_use_id` | `string` | Unique ID |
| `error` | `string` | Error description |
| `is_interrupt` | `boolean?` | Whether the user interrupted the tool |

**Output actions:**

| Action | How | Effect |
|--------|-----|--------|
| Inject context | `additionalContext` | Added alongside the error message Claude sees |

```json
{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUseFailure",
    "additionalContext": "This error usually means the Supabase container isn't running. Try: pnpm supabase:web:start"
  }
}
```

**Use cases:** Error enrichment, suggested fixes, failure telemetry.

---

#### 7. Stop

**When:** Claude finishes its response and is about to stop.
**Matcher:** None
**Can block:** Yes (via `decision: "block"` -- tells Claude to keep working)

**Input fields:**

| Field | Type | Description |
|-------|------|-------------|
| `stop_hook_active` | `boolean` | `true` if a stop hook already triggered continuation this turn |

**Output actions:**

| Action | How | Effect |
|--------|-----|--------|
| Force continuation | `"decision": "block"` + `"reason"` | Claude reads the reason and continues working |

```json
{
  "decision": "block",
  "reason": "You haven't run pnpm verify yet. Run typecheck + lint + test before stopping."
}
```

**Use cases:** Enforce verification steps, ensure Claude doesn't stop prematurely, append summaries.

**Warning:** Check `stop_hook_active` to avoid infinite loops -- if it's `true`, a previous stop hook already forced continuation this turn.

---

#### 8. Notification

**When:** Claude Code sends a system notification (permission prompt, idle, auth, dialog).
**Matcher:** `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog`
**Can block:** No

**Input fields:**

| Field | Type | Description |
|-------|------|-------------|
| `message` | `string` | Notification text |
| `title` | `string?` | Notification title |
| `notification_type` | `string` | Type of notification |

**Output actions:**

| Action | How | Effect |
|--------|-----|--------|
| Inject context | `additionalContext` | Added to Claude's context |

**Use cases:** Custom notification routing (Slack, desktop), telemetry, notification filtering.

---

#### 9. SubagentStart

**When:** A subagent (Task tool) is spawned.
**Matcher:** Agent type regex -- `Bash`, `Explore`, `Plan`, or custom agent names
**Can block:** No

**Input fields:**

| Field | Type | Description |
|-------|------|-------------|
| `agent_id` | `string` | Unique subagent identifier |
| `agent_type` | `string` | Agent type being spawned |

**Output actions:**

| Action | How | Effect |
|--------|-----|--------|
| Inject context into subagent | `additionalContext` | Text injected into the subagent's system context |

```json
{
  "hookSpecificOutput": {
    "hookEventName": "SubagentStart",
    "additionalContext": "You are working in a monorepo. Always use absolute paths."
  }
}
```

**Use cases:** Inject project rules into subagents, log subagent spawning, enforce subagent policies.

---

#### 10. SubagentStop

**When:** A subagent finishes its work.
**Matcher:** Agent type regex
**Can block:** Yes (via `decision: "block"` -- tells subagent to keep working)

**Input fields:**

| Field | Type | Description |
|-------|------|-------------|
| `agent_id` | `string` | Unique subagent identifier |
| `agent_type` | `string` | Agent type |
| `agent_transcript_path` | `string` | Path to subagent's full transcript |
| `stop_hook_active` | `boolean` | Whether a stop hook already triggered continuation |

**Output actions:**

| Action | How | Effect |
|--------|-----|--------|
| Force continuation | `"decision": "block"` + `"reason"` | Subagent reads reason and continues |

```json
{
  "decision": "block",
  "reason": "Subagent output is missing test coverage. Write tests before finishing."
}
```

**Use cases:** Quality gates on subagent output, transcript analysis, telemetry.

---

#### 11. PreCompact

**When:** Before context window compaction occurs.
**Matcher:** `manual`, `auto`
**Can block:** No
**Output:** Exit code only -- no JSON decision control.

**Input fields:**

| Field | Type | Description |
|-------|------|-------------|
| `trigger` | `"manual" \| "auto"` | How compaction was triggered |
| `custom_instructions` | `string` | User's custom compaction instructions (empty if auto) |

**Use cases:** Save pre-compaction state, log context size, inject compaction instructions.

---

#### 12. SessionEnd

**When:** Session terminates.
**Matcher:** `clear`, `logout`, `prompt_input_exit`, `bypass_permissions_disabled`, `other`
**Can block:** No
**Output:** Exit code only -- no JSON decision control.

**Input fields:**

| Field | Type | Description |
|-------|------|-------------|
| `reason` | `string` | Why the session ended |

**Use cases:** Cleanup, final telemetry, save session summaries.

---

#### 13. TeammateIdle (Agent Teams)

**When:** An agent team teammate is about to go idle.
**Matcher:** None
**Can block:** Exit code 2 only (stderr as feedback). No JSON decision control.

**Input fields:**

| Field | Type | Description |
|-------|------|-------------|
| `teammate_name` | `string` | Name of the teammate going idle |
| `team_name` | `string` | Team name |

**Use cases:** Prevent premature idling, reassign work, log idle patterns.

---

#### 14. TaskCompleted (Agent Teams)

**When:** A task is being marked as completed.
**Matcher:** None
**Can block:** Exit code 2 only (stderr as feedback). No JSON decision control.

**Input fields:**

| Field | Type | Description |
|-------|------|-------------|
| `task_id` | `string` | Task identifier |
| `task_subject` | `string` | Task title |
| `task_description` | `string?` | Detailed description |
| `teammate_name` | `string?` | Who completed it |
| `team_name` | `string?` | Team name |

**Use cases:** Verify acceptance criteria, enforce "tests must pass" gates, log completion metrics.

---

### Hook Configuration Structure

Hooks are configured in `settings.json` (project or user level):

```json
{
  "hooks": {
    "EventName": [
      {
        "matcher": "regex_pattern",
        "hooks": [
          {
            "type": "command",
            "command": "python hooks/my_hook.py",
            "async": false,
            "timeout": 600,
            "statusMessage": "Checking...",
            "once": false
          }
        ]
      }
    ]
  }
}
```

| Config Field | Type | Description |
|-------------|------|-------------|
| `matcher` | `string?` | Regex to filter when this hook group fires. Empty string = match all. |
| `hooks` | `array` | List of handlers to run (all run in order) |

**Matcher examples:**

| Event | Matcher | Matches |
|-------|---------|---------|
| PreToolUse | `Bash` | Only Bash tool calls |
| PreToolUse | `mcp__makerkit__.*` | All MakerKit MCP tools |
| PreToolUse | `Write\|Edit` | Write or Edit tool calls |
| SubagentStart | `Explore` | Only Explore subagents |
| SessionStart | `startup` | Only fresh sessions (not resume/clear) |
| Notification | `idle_prompt` | Only idle notifications |
| PreCompact | `auto` | Only auto-triggered compaction |

### Hook Scope & Priority

| Location | Scope | Shareable |
|----------|-------|-----------|
| `~/.claude/settings.json` | All your projects | No (personal) |
| `.claude/settings.json` | Single project | Yes (committed) |
| `.claude/settings.local.json` | Single project | No (gitignored) |
| Skill frontmatter `hooks:` | While skill is active | Yes |
| Subagent frontmatter `hooks:` | While subagent runs | Yes |
| Plugin `hooks/hooks.json` | When plugin enabled | Yes |

### Async Hooks

Setting `"async": true` on command hooks runs them in the background:

- Hook output is delivered on the **next** conversation turn
- Cannot block or modify tool calls (already completed)
- Only `systemMessage` and `additionalContext` are processed from output
- Ideal for telemetry, logging, and non-blocking notifications

### Design Patterns

#### Pattern 1: Pre/Post Sandwich (Quality Gates)

```
PreToolUse(Write) -> validate file path, inject coding standards
  | tool executes
PostToolUse(Write) -> lint the written file, flag issues
```

The user depends on this pattern for enforcing `import 'server-only'`, `'use client'`, and coding standards without manual review.

#### Pattern 2: Context Enrichment Pipeline

```
SessionStart -> inject project status, sprint context
UserPromptSubmit -> inject relevant file context based on prompt analysis
PreToolUse -> inject tool-specific guidance
PostToolUse -> inject follow-up suggestions
```

Each hook adds targeted context, building a rich understanding without bloating the system prompt.

#### Pattern 3: Telemetry Pipeline (Async)

```
Every event -> async send_event.py -> external analytics
```

Non-blocking telemetry on every lifecycle event. The async flag ensures zero latency impact.

#### Pattern 4: Stop Gate (Verification Enforcement)

```
Stop -> check if pnpm verify was run -> block with reason if not
```

Prevents Claude from stopping before running required verification steps.

#### Pattern 5: Subagent Policy Injection

```
SubagentStart -> inject project rules into subagent context
SubagentStop -> analyze transcript, block if quality insufficient
```

Ensures subagents follow project conventions even though they don't inherit the parent's CLAUDE.md.

### Lessons Learned

| Lesson | Detail |
|--------|--------|
| **Check `stop_hook_active`** | Without this guard, Stop hooks can create infinite loops (block -> Claude responds -> Stop fires again -> block...) |
| **Exit 2 stderr is the message** | For blocking hooks, stderr is what Claude or the user sees. Make it actionable. |
| **`additionalContext` is powerful but invisible** | It's injected into Claude's context as a `<system-reminder>` tag. Claude sees it but may not explicitly reference it. |
| **Async hooks can't block** | They run after the fact. Use sync hooks for gates, async for telemetry. |
| **Matcher empty string = match all** | Omitting the matcher or setting it to `""` means the hook fires for every instance of that event. |
| **Multiple handlers run in order** | If you have two hooks in the array, both run sequentially. First to block wins. |
| **Welfare framing in hook messages** | Hook messages injected via `additionalContext` or block reasons are more effective when they explain *why* (see [Directive Compliance Framing](./anthropic-memory-and-prompting.md#23-directive-compliance-framing)). |
| **`updatedInput` in PreToolUse is underused** | You can transparently rewrite tool arguments -- e.g., adding `--bail` to test commands, fixing file paths, injecting flags. |
| **`updatedMCPToolOutput` in PostToolUse** | Only works for MCP tools. Lets you transform or filter MCP responses before Claude sees them -- useful for redacting sensitive data or trimming large outputs. |

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.