# Claude Code Hooks
Hooks let you run shell commands at specific points in Claude Code's lifecycle. Configured in `settings.json` under the `hooks` key.
## Available hook events
| Hook | Fires | Can block? |
|------|-------|------------|
| `SessionStart` | Session starts or resumes. Runs in both local and cloud environments | — |
| `SessionEnd` | Session closes (timeout configurable via `CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS`) | — |
| `UserPromptSubmit` | User submits a prompt — before Claude processes it. Useful for pre-processing input, logging, or injecting context | ✅ |
| `PreToolUse` | Before a tool call is executed | ✅ |
| `PostToolUse` | After a tool call completes successfully | — |
| `PostToolUseFailure` | After a tool call fails | — |
| `SubagentStart` | A subagent spawns | — |
| `SubagentStop` | A subagent completes | ✅ |
| `TaskCreated` | A task is added to the task list | — |
| `TaskCompleted` | A task is marked complete | — |
| `CwdChanged` | Working directory changes during a session | — |
| `FileChanged` | A file is modified by Claude | — |
| `PermissionRequest` | A permission dialog is shown | ✅ |
| `PermissionDenied` | Auto mode classifier denies an action (auto mode only) | — |
| `PreCompact` | Before context compression (manual or auto) | ✅ (v2.1.105+) |
| `PostCompact` | After context compression | — |
| `InstructionsLoaded` | CLAUDE.md or `.claude/rules/` files load (v2.1.69+) | — |
| `ConfigChange` | Config changes during a session | ✅ (v2.1.49+) |
| `Stop` | Claude finishes generating a response | ✅ |
| `Notification` | Claude Code sends a notification | — |
`CLAUDE_ENV_FILE` is also available for `CwdChanged` and `FileChanged` hooks (write env vars to it to persist them across subsequent Bash commands).
## Handler types
A hook entry can use one of four handler types:
| Type | Behavior |
|------|----------|
| `command` | Runs a shell command. Fast, deterministic, predictable. Default choice |
| `prompt` | Lets a smaller LLM decide via prompt. Flexible and context-aware, but adds latency and cost |
| `http` | POSTs JSON payload to an external URL with optional auth headers (v2.1.63+) |
| `async: true` | Modifier flag — runs the hook in the background without blocking the agent loop |
### `prompt` handler example
```json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Evaluate if Claude should stop: check if all tasks are complete",
"timeout": 30
}
]
}
]
}
}
```
### `http` handler example
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "http",
"url": "https://api.example.com/notify",
"headers": {"Authorization": "Bearer $MY_TOKEN"},
"allowedEnvVars": ["MY_TOKEN"]
}
]
}
]
}
}
```
`allowedEnvVars` whitelists which env vars from the host environment are interpolated into headers — keeps secrets out of unrelated hook calls.
### `async: true` example
```json
{
"type": "command",
"command": ".claude/hooks/notify-slack.sh",
"async": true
}
```
Use for fire-and-forget side effects (notifications, logging) that shouldn't slow Claude down.
## SessionStart hooks
`SessionStart` hooks run after Claude Code launches, on every session start including resumed sessions. Useful for dependency installation, environment setup, or injecting context.
- Hooks can persist environment variables for subsequent Bash commands by writing to the file at `$CLAUDE_ENV_FILE`
- Check `$CLAUDE_CODE_REMOTE` to conditionally skip local execution (e.g., only install deps in cloud sessions)
- User-level hooks (`~/.claude/settings.json`) only run locally; in cloud sessions, only repo-level hooks (`.claude/settings.json`) execute
For cloud-only setup, prefer setup scripts over hooks. See [[Claude Code Web]] for the comparison table.
### SessionStart matcher values
| Matcher | When it fires |
|---------|--------------|
| `startup` | New session |
| `resume` | `--resume`, `--continue`, `/resume` |
| `clear` | `/clear` |
| `compact` | Auto or manual compaction |
### Persisting environment variables
Write to `$CLAUDE_ENV_FILE` from a SessionStart hook to make env vars available to all subsequent Bash commands:
```bash
#!/bin/bash
if [ -n "$CLAUDE_ENV_FILE" ]; then
echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
fi
exit 0
```
`CLAUDE_ENV_FILE` is also available for `CwdChanged` and `FileChanged` hooks.
## Conditional hooks with `if`
The `if` field uses permission rule syntax to filter when a hook handler runs within a matched group. Only evaluated on tool events (`PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `PermissionDenied`). Avoids spawning the handler process when the condition doesn't match.
```json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": ".claude/hooks/block-rm.sh"
}
]
}
]
}
}
```
Here `matcher: "Bash"` filters to Bash tool calls, then `if: "Bash(rm *)"` further narrows to commands starting with `rm`. A command like `npm test` passes the matcher but fails the `if` check and never spawns the handler.
More examples: `"if": "Edit(*.ts)"` to lint only TypeScript edits; `"if": "Bash(git push *)"` to guard pushes.
## Hook return values
Hooks signal decisions back to Claude Code via exit code or structured JSON on stdout.
### Exit codes (simple path)
| Code | Meaning |
|------|---------|
| `0` | Success. Stdout shown only in verbose mode |
| `1`, `3` | Warning. Stderr shown in verbose mode |
| `2` | **Blocking error**. Stderr becomes the error message shown to Claude |
Exit `2` from a `PreToolUse` hook cancels the tool call; from `Stop`, blocks Claude from stopping; from `UserPromptSubmit`, drops the prompt before Claude sees it.
### Structured JSON output (advanced)
Print a JSON object to stdout for fine-grained control:
```json
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "Command validated against allowlist",
"updatedInput": {"command": "npm test -- --ci"},
"additionalContext": "Note: 5s query timeout"
}
}
```
Key fields:
- `permissionDecision` — `"allow"`, `"deny"`, or `"ask"` (overrides the normal permission flow)
- `permissionDecisionReason` — string shown to the user/agent explaining the decision
- `updatedInput` — **rewrites the tool's arguments before execution** (powerful: a `PreToolUse` hook can sanitize commands, add flags, redirect paths, etc.)
- `additionalContext` — extra context surfaced to Claude after the hook runs
## Example
```json
{
"hooks": {
"UserPromptSubmit": [
{
"matcher": "",
"hooks": [
{ "type": "command", "command": "echo 'Prompt submitted' >> ~/claude-log.txt" }
]
}
]
}
}
```
## References
- Hooks docs: https://code.claude.com/docs/en/hooks
- `if` field announcement: https://x.com/lydiahallie/status/2037573738670297583
## Related
- [[Claude Code]]
- [[Claude Code Configuration]]
- [[Claude Code Skills]]
- [[Claude Code Web]]