Settings & Hooks¶
Hooks provide deterministic automation — unlike prompts (which are suggestions), hooks guarantee execution on specific events.
Why Hooks Over Prompts¶
| Prompts | Hooks |
|---|---|
| Suggestions Claude might follow | Guaranteed execution |
| Can be forgotten in long contexts | Always run |
| Require Claude to remember | Event-driven automation |
| Non-deterministic | Fully deterministic |
Hook Events¶
| Event | When It Fires | Best For |
|---|---|---|
PreToolUse |
Before tool execution | Validation, blocking dangerous operations |
PostToolUse |
After successful execution | Formatting, linting, tests |
UserPromptSubmit |
When you submit a prompt | Adding context, validation |
PermissionRequest |
When Claude asks permission | Auto-approve/deny rules |
Stop |
When Claude finishes responding | End-of-turn quality gates |
SubagentStart |
When a subagent is spawned | Subagent-specific setup |
SubagentStop |
When subagent finishes | Subagent-specific cleanup |
SessionStart |
When session begins | Environment setup |
SessionEnd |
When session ends | Cleanup, notifications |
These are the most common events. See the official hooks reference for the full list (21+ events including PostToolUseFailure, Notification, WorktreeCreate, PreCompact, and more).
Exit Codes¶
| Exit Code | Meaning |
|---|---|
0 |
Success — continue normally |
2 |
Blocking error — operation is blocked, stderr is fed back to Claude |
| Any other | Non-blocking error — stderr is logged (verbose mode only), execution continues |
Essential Hook Configurations¶
Auto-Lint on Edit (Frontend + Backend)¶
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs -I {} sh -c 'case {} in *.ts|*.tsx|*.js|*.jsx) npx prettier --write {} && npx eslint --fix {} ;; *.py) uv run ruff check --fix {} && uv run ruff format {} ;; esac'"
}
]
}
]
}
}
Block Dangerous Operations¶
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.command' | grep -qE '(rm -rf /|DROP TABLE|DELETE FROM.*WHERE 1|> /dev/sd)' && echo 'Blocked dangerous command' >&2 && exit 2 || exit 0"
}
]
}
]
}
}
Auto-Approve Safe Operations¶
Use permission rules in settings.json instead of hooks for auto-approving tools:
{
"permissions": {
"allow": [
"Read",
"Glob",
"Grep",
"Bash(git status *)",
"Bash(git diff *)"
]
}
}
End-of-Turn Quality Gate¶
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "./scripts/quality-gate.sh"
}
]
}
]
}
}
Example scripts/quality-gate.sh:
#!/bin/bash
set -e
# Frontend checks
if [ -f "package.json" ]; then
npx tsc --noEmit 2>/dev/null || true
fi
# Backend checks
if [ -f "pyproject.toml" ]; then
uv run mypy src/ --ignore-missing-imports 2>/dev/null || true
fi
Matcher Patterns¶
# Match specific tools (regex on tool name)
"matcher": "Edit|Write"
# Match a specific tool
"matcher": "Bash"
# Match MCP tools
"matcher": "mcp__.*"
# Match everything
"matcher": "*"
Full settings.json Template¶
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "./scripts/post-edit.sh"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "./scripts/quality-gate.sh"
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "./scripts/validate-command.sh"
}
]
}
]
},
"permissions": {
"allow": [
"Read",
"Glob",
"Grep",
"Edit",
"Write",
"Bash(git *)",
"Bash(npm *)",
"Bash(uv *)",
"Bash(pytest *)"
],
"deny": [
"Bash(rm -rf *)",
"Bash(sudo *)",
"Bash(curl *)",
"Bash(wget *)"
]
}
}
Viewing Hooks¶
This opens a read-only browser showing your configured hooks. To add or modify hooks, edit the settings JSON directly or ask Claude to make the change.
Tips¶
- Start with auto-lint — the highest-value hook with minimal friction
- Test hooks on one machine before rolling out to the team
- Keep hook scripts fast — slow hooks disrupt the development flow
- Use exit code 2 sparingly — blocking is disruptive; prefer warnings (exit code 1)
- Externalize complex logic to shell scripts rather than inline JSON commands