What hooks enable
Hooks add an automation layer to Claude Code without modifying its code. Common uses:
- Auto-format files after writing
- Block dangerous commands (
rm -rf,git push --force) - Generate session reports on stop
- Preserve key info before compaction
- Ask an LLM “is this change safe?” and block on the answer
Configuration locations
| File | Scope |
|---|---|
~/.claude/settings.json | User-global |
.claude/settings.json | Project (committed) |
.claude/settings.local.json | Personal local (gitignored) |
Basic structure
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_response.filePath // .tool_input.file_path' | { read -r f; prettier --write \"$f\"; } 2>/dev/null || true"
}
]
}
]
}
}
Common events
| Event | When | Use case |
|---|---|---|
| PreToolUse | Before a tool runs | Block dangerous commands, permission checks |
| PostToolUse | After a tool runs | Auto-format, run tests |
| PostToolUseFailure | After a tool fails | Error logging, notifications |
| UserPromptSubmit | When user submits | Prompt analysis, context injection |
| Stop | Session end | Generate reports, cleanup |
| PreCompact | Before compaction | Preserve key info separately |
| SessionStart | Session start | Environment check, intro message |
Three hook types
1. command — run a shell command
{
"type": "command",
"command": "jq -r '.tool_input.command' >> ~/.claude/bash-log.txt"
}
Receives the event JSON on stdin. Use jq to extract fields.
2. prompt — ask an LLM
{
"type": "prompt",
"prompt": "Decide if this command makes a destructive system change. Answer 'block' or 'allow': $ARGUMENTS"
}
The LLM answer drives block/allow. Runs on a small fast model.
3. agent — run an agent
{
"type": "agent",
"prompt": "Verify that unit tests were added alongside the file changes"
}
For complex verification. Higher token cost.
Example: block dangerous commands
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "prompt",
"prompt": "Decide if this shell command permanently deletes data or makes hard-to-reverse changes. Answer 'block' or 'allow': $ARGUMENTS"
}
]
}
]
}
}
Example: auto-format after write
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_response.filePath // .tool_input.file_path' | { read -r f; case \"$f\" in *.ts|*.tsx|*.js|*.jsx|*.json|*.md) prettier --write \"$f\";; esac; } 2>/dev/null || true"
}
]
}
]
}
}
Per-extension formatter selection.
Example: stop-time report
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "echo '{\"systemMessage\": \"Session ended — review changed files before closing.\"}'"
}
]
}
]
}
}
JSON output with systemMessage is shown to the user.
Debugging checklist
- JSON syntax —
jq . settings.jsonto validate - Matcher pattern — exact tool name (e.g.
Write|Edit) - Test the command directly —
echo '{"tool_input":{"file_path":"test.ts"}}' | <hook command> - Restart — new hooks need a Claude Code restart or one open of
/hooksso the watcher picks them up - Debug mode —
claude --debugshows hook execution logs
Use hookify
hookify generates hooks from natural language. If hand-writing JSON feels heavy, draft with hookify first and apply the result to settings.json.
Next steps
- hookify — define hooks in plain language
- Claude Code Setup — project-aware hook recommendations
- Writing a Great CLAUDE.md — separate hook automation from CLAUDE.md rules
Frequently Asked Questions
What are hooks?
Shell commands or LLM prompts that run automatically on specific events (before/after a tool call, session start/end, compaction, etc.). They add an automation layer to Claude Code without changing any code.
Where do I configure them?
In the `hooks` field of `~/.claude/settings.json` (user-global), `.claude/settings.json` (project-shared), or `.claude/settings.local.json` (personal local).
Which events are supported?
PreToolUse, PostToolUse, PostToolUseFailure, UserPromptSubmit, Stop, SessionStart, SessionEnd, PreCompact, PostCompact, and more. PreToolUse and PostToolUse are the most common for automation around tool calls.
Can hooks use an LLM instead of running code?
Yes. Use `type: "prompt"` for LLM-based decisions, or `type: "agent"` to run a small agent for more complex verification.
How do I debug a hook that isn't firing?
Most issues come from JSON syntax errors or matcher patterns that don't match. Run `claude --debug` for hook execution logs and `jq -e` to validate JSON. New hooks need a restart or one open of `/hooks` for the watcher to pick them up.