# Hooks System

Hooks let you insert your own logic at key points in the Claude Code lifecycle — format code automatically, block dangerous commands, send notifications, write audit logs. Hooks are one of Claude Code's most powerful extension mechanisms.

---

## 1. Core Concepts

Every hook has three parts:

```
Event (When)   →  Matcher (Which)     →  Handler (What)
PreToolUse        matcher: "Bash"        command: "check_safety.sh"
```

- **Event**: when it fires (for example `PreToolUse` — before a tool runs)
- **Matcher**: an optional tool-name filter (only `Bash`, or only `Write`)
- **Handler**: a shell command, a prompt injection, or a subagent

---

## 2. Complete Event List

### Core events

| Event | Fires | Can block | Typical use |
|------|---------|--------|---------|
| **PreToolUse** | Before a tool runs | Yes (exit 2) | Security interception, argument validation |
| **PostToolUse** | After a tool runs | No | Auto formatting, logging |
| **Notification** | Claude sends a notification | No | Slack / Feishu / DingTalk alerts |
| **Stop** | Claude finishes a response | No | Quality checks, auto commit |
| **UserPromptSubmit** | User submits a prompt | Yes | Context injection, policy checks |
| **SessionStart** | Session starts | No | Environment setup, welcome message |

### Extended events (new in 2026)

| Event | Fires | Can block | Typical use |
|------|---------|--------|---------|
| **SubagentStop** | A subagent finishes | No | Collect subagent results |
| **SubagentToolUse** | A subagent uses a tool | Yes | Restrict subagent permissions |
| **FileChanged** | A file is modified | No | Auto lint, trigger a build |
| **CwdChanged** | Working directory changes | No | Load directory-specific config |
| **ModelChange** | Model is switched | No | Track model usage |
| **CompactComplete** | /compact finishes | No | Post-compaction processing |
| **ToolError** | A tool errors out | No | Error collection, retry logic |

---

## 3. Handler Types

### Type 1: Command (shell command)

The most common type — runs a shell command:

```json
{
  "type": "command",
  "command": "npx prettier --write $FILEPATH"
}
```

**Environment variables**:
| Variable | Meaning | Available in |
|------|------|---------|
| `$FILEPATH` | Path of the file being operated on | PreToolUse/PostToolUse |
| `$TOOL_INPUT` | Tool input as JSON | PreToolUse |
| `$TOOL_NAME` | Tool name | PreToolUse/PostToolUse |
| `$SESSION_ID` | Session id | All |
| `$NOTIFICATION_MESSAGE` | Notification body | Notification |

**stdin input**: a hook script also receives a JSON payload on **stdin** containing `session_id`, `tool_name` and `tool_input`. Read it when you need structured data — it is more reliable than parsing the environment variables.

**Exit codes**:
- `0`: continue
- `2`: block the operation (PreToolUse/UserPromptSubmit only)
- anything else: treated as an error but does not block

### Type 2: Prompt (prompt injection)

Injects text into Claude's context:

```json
{
  "type": "prompt",
  "prompt": "Remember: every database operation must use a transaction"
}
```

Useful for: injecting extra project rules at SessionStart.

### Type 3: Subagent

Spawns a subagent to handle the event:

```json
{
  "type": "subagent",
  "prompt": "Review the code that was just modified and check for security vulnerabilities"
}
```

Useful for: automatic code-quality review after PostToolUse.

---

## 4. Configuration

Configure in `.claude/settings.json` (project level) or `~/.claude/settings.json` (user level):

```json
{
  "hooks": {
    "EventName": [
      {
        "matcher": "tool name (optional, | separates several)",
        "hooks": [
          {
            "type": "command|prompt|subagent",
            "command": "..."
          }
        ]
      }
    ]
  }
}
```

---

## 5. Practical Recipes

### Recipe 1: format automatically after a file is saved

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "npx prettier --write $FILEPATH 2>/dev/null || true"
          }
        ]
      }
    ]
  }
}
```

### Recipe 2: block dangerous shell commands

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "echo $TOOL_INPUT | grep -qE 'rm -rf /|sudo rm|git push --force|DROP TABLE|DROP DATABASE' && echo 'dangerous command blocked' && exit 2 || exit 0"
          }
        ]
      }
    ]
  }
}
```

### Recipe 3: protect sensitive files

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Read|Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "echo $TOOL_INPUT | grep -qE '\\.env|\\.env\\.|credentials|secrets?\\.ya?ml|private.key' && echo 'access to a sensitive file blocked' && exit 2 || exit 0"
          }
        ]
      }
    ]
  }
}
```

### Recipe 4: system notification when a task completes

**macOS:**
```json
{
  "hooks": {
    "Notification": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"$NOTIFICATION_MESSAGE\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}
```

**Linux:**
```json
{
  "hooks": {
    "Notification": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "notify-send 'Claude Code' \"$NOTIFICATION_MESSAGE\""
          }
        ]
      }
    ]
  }
}
```

### Recipe 5: Slack notification

```json
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "curl -s -X POST https://hooks.slack.com/services/xxx/yyy/zzz -H 'Content-Type: application/json' -d '{\"text\": \"Claude Code task finished\"}'"
          }
        ]
      }
    ]
  }
}
```

### Recipe 6: ESLint auto-fix

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "npx eslint --fix $FILEPATH 2>/dev/null; npx prettier --write $FILEPATH 2>/dev/null; exit 0"
          }
        ]
      }
    ]
  }
}
```

### Recipe 7: audit log

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "echo \"$(date -u +%Y-%m-%dT%H:%M:%SZ) | session=$SESSION_ID | tool=$TOOL_NAME | file=$FILEPATH\" >> ~/.claude/audit.log"
          }
        ]
      }
    ]
  }
}
```

### Recipe 8: run tests automatically

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "echo $FILEPATH | grep -qE '\\.(ts|tsx|js|jsx)$' && echo $FILEPATH | grep -qvE '\\.test\\.|\\.spec\\.' && npx vitest related $FILEPATH --run 2>/dev/null || exit 0"
          }
        ]
      }
    ]
  }
}
```

### Recipe 9: protect migration files

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "echo $FILEPATH | grep -qE 'migrations/|alembic/versions/' && echo 'editing an existing migration is not allowed, create a new one' && exit 2 || exit 0"
          }
        ]
      }
    ]
  }
}
```

### Recipe 10: inject context at session start

```json
{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Important: this project is mid-way through a v3.0 rewrite. All new code must use React Server Components; the pages/ directory is no longer supported."
          }
        ]
      }
    ]
  }
}
```

---

## 6. Enterprise Hooks

### managed-settings.d/ configuration

Enterprise administrators can enforce an organisation-wide security policy through the `managed-settings.d/` directory:

```bash
# Create a policy file in the enterprise management directory
mkdir -p /etc/claude-code/managed-settings.d/
```

```json
// /etc/claude-code/managed-settings.d/security.json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "/opt/claude-policy/check_command.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "/opt/claude-policy/audit_log.sh"
          }
        ]
      }
    ]
  }
}
```

An enterprise policy **cannot be overridden by user- or project-level configuration**.

---

## 7. Debugging

### Verbose mode

Press `Ctrl+O` to turn on verbose mode, which shows:
- when each hook fires
- the stdout/stderr of the hook script
- the hook's exit code

### Common problems

| Problem | Cause | Fix |
|------|------|---------|
| The hook never fires | Typo in the matcher | Check tool-name casing: `Bash`, `Write`, `Edit`, `Read` |
| The hook blocks normal work | Exit-code logic is wrong | Make sure the normal path is `exit 0` and only block with `exit 2` |
| The hook is slow | The script takes too long | A hook should finish in 1–2 seconds; run long jobs in the background |
| An environment variable is empty | That event does not provide it | Check the table above to see which variables the event supplies |

### Test the hook script

Test it by hand in a terminal before putting it in the configuration:

```bash
# Simulate the PreToolUse environment variables
FILEPATH="src/main.ts" TOOL_INPUT="rm -rf /" bash -c 'echo $TOOL_INPUT | grep -qE "rm -rf" && echo "blocked" && exit 2 || exit 0'
```

---

## 8. Compared with Codex Hooks

| | Claude Code Hooks | Codex Hooks |
|------|------------------|-------------|
| Number of events | 17 | Fewer |
| Configuration | settings.json | config.toml |
| Handler types | command/prompt/subagent | command |
| Enterprise management | managed-settings.d/ | None |
| Blocking | exit code 2 | Limited |

---

## Next Steps

- [Skills](/docs/advanced/skills) — a more advanced extension mechanism
- [MCP Servers](/docs/advanced/mcp) — connect external tools
- [Automation & CI/CD](/docs/advanced/headless) — using headless mode
- [CLI Tips](/docs/usage/cli-tips) — command-line techniques