Hooks run commands at specific points in Claude Code's lifecycle.
```json { "hooks": { "EVENT_NAME": [ { "matcher": "ToolName|OtherTool", "hooks": [ { "type": "command", "command": "your-command-here", "timeout": 60, "statusMessage": "Running..." } ] } ] } } ```
| Event | Matcher | Purpose |
|---|---|---|
| PermissionRequest | Tool name | Run before permission prompt |
| PreToolUse | Tool name | Run before tool, can block |
| PostToolUse | Tool name | Run after successful tool |
| PostToolUseFailure | Tool name | Run after tool fails |
| Notification | Notification type | Run on notifications |
| Stop | - | Run when Claude stops (including clear, resume, compact) |
| PreCompact | "manual"/"auto" | Before compaction |
| UserPromptSubmit | - | When user submits |
| SessionStart | - | When session starts |
Common tool matchers: `Bash`, `Write`, `Edit`, `Read`, `Glob`, `Grep`
1. Command Hook - Runs a shell command: ```json { "type": "command", "command": "prettier --write $FILE", "timeout": 30 } ```
2. Prompt Hook - Evaluates a condition with LLM: ```json { "type": "prompt", "prompt": "Is this safe? $ARGUMENTS" } ``` Only available for tool events: PreToolUse, PostToolUse, PermissionRequest.
3. Agent Hook - Runs an agent with tools: ```json { "type": "agent", "prompt": "Verify tests pass: $ARGUMENTS" } ``` Only available for tool events: PreToolUse, PostToolUse, PermissionRequest.
```json { "session_id": "abc123", "tool_name": "Write", "tool_input": { "file_path": "/path/to/file.txt", "content": "..." }, "tool_response": { "success": true } // PostToolUse only } ```
Hooks can return JSON to control behavior:
```json { "systemMessage": "Warning shown to user in UI", "continue": false, "stopReason": "Message shown when blocking", "suppressOutput": false, "decision": "block", "reason": "Explanation for decision", "hookSpecificOutput": { "hookEventName": "PostToolUse", "additionalContext": "Context injected back to model" } } ```
Fields:
- `systemMessage` - Display a message to the user (all hooks)
- `continue` - Set to `false` to block/stop (default: true)
- `stopReason` - Message shown when `continue` is false
- `suppressOutput` - Hide stdout from transcript (default: false)
- `decision` - "block" for PostToolUse/Stop/UserPromptSubmit hooks (deprecated for PreToolUse, use hookSpecificOutput.permissionDecision instead)
- `reason` - Explanation for decision
- `hookSpecificOutput` - Event-specific output (must include `hookEventName`):
- `additionalContext` - Text injected into model context
- `permissionDecision` - "allow", "deny", or "ask" (PreToolUse only)
- `permissionDecisionReason` - Reason for the permission decision (PreToolUse only)
- `updatedInput` - Modified tool input (PreToolUse only)
Auto-format after writes: ```json { "hooks": { "PostToolUse": [{ "matcher": "Write|Edit", "hooks": [{ "type": "command", "command": "jq -r '.tool_response.filePath // .tool_input.file_path' | xargs prettier --write 2>/dev/null || true" }] }] } } ```
Log all bash commands: ```json { "hooks": { "PreToolUse": [{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "jq -r '.tool_input.command' >> ~/.claude/bash-log.txt" }] }] } } ```
Stop hook that displays message to user:
Command must output JSON with `systemMessage` field: ```bash
echo '{"systemMessage": "Session complete!"}' ```
Run tests after code changes: ```json { "hooks": { "PostToolUse": [{ "matcher": "Write|Edit", "hooks": [{ "type": "command", "command": "jq -r '.tool_input.file_path // .tool_response.filePath' | grep -E '\\.(ts|js)$' && npm test || true" }] }] } } ```