Most Claude Code users know about skills and slash commands. Fewer know about hooks — and the ones who do use them to build automations that feel like magic.
Hooks let you run any shell command automatically in response to Claude Code events. Before a tool executes. After a tool completes. Every time Claude stops responding. When a subagent finishes. These are programmable triggers that plug directly into Claude Code’s execution lifecycle.
If you’ve ever wanted Claude Code to automatically run your test suite after every code change, log every file it touches, or notify you via Slack when a long task finishes — hooks are how you do it.
What Are Claude Code Hooks?
Hooks are shell commands (or arrays of shell commands) that Claude Code executes at specific lifecycle events. They’re configured in ~/.claude/settings.json under the hooks key.
There are five hook types:
| Hook | Fires When |
|---|---|
PreToolUse | Before any tool is called |
PostToolUse | After a tool completes |
Stop | When Claude finishes responding |
SubagentStop | When a subagent finishes |
Notification | When Claude Code sends a system notification |
Each hook receives context about what just happened — the tool name, inputs, outputs, and event details — so your scripts can make decisions based on what Claude is actually doing.
Setting Up Your First Hook
Hooks live in ~/.claude/settings.json. Open it (or create it) and add a hooks section:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "echo 'File written' >> ~/.claude/activity.log"
}
]
}
]
}
}
This logs every time Claude Code writes a file. The matcher field filters which tool triggers the hook — you can match specific tools like Write, Edit, Bash, or use ".*" to match everything.
The Five Hook Types in Detail
PreToolUse — Run Before Any Tool
Use PreToolUse to validate, log, or block tool execution before it happens.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo \"[$(date -Iseconds)] BASH: $CLAUDE_TOOL_INPUT\" >> ~/.claude/bash-audit.log"
}
]
}
]
}
}
This creates an audit trail of every Bash command Claude executes. Useful for security reviews and debugging long autonomous runs.
Pro tip: If a PreToolUse hook exits with a non-zero code, Claude Code will see it and can abort or modify its approach.
PostToolUse — React to What Claude Just Did
This is the most versatile hook type. Run tests after an edit, format code after a write, sync files after changes:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "cd $(git rev-parse --show-toplevel 2>/dev/null || echo '.') && git diff --stat HEAD 2>/dev/null | tail -1 >> ~/.claude/changes.log"
}
]
}
]
}
}
This logs a git diff summary every time Claude edits or creates a file — a running change log of everything it modified.
Stop — When the Conversation Ends
The Stop hook fires whenever Claude finishes a response. Use it to run comprehensive checks after a task completes:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "cd $(git rev-parse --show-toplevel 2>/dev/null || echo '.') && [ -f 'package.json' ] && npm test --silent 2>&1 | tail -5 || true"
}
]
}
]
}
}
This auto-runs your test suite silently after every Claude session. If tests break, you’ll know immediately — Claude’s output shows the tail of the test results.
SubagentStop — React to Subagent Completions
When Claude spawns subagents (via the Agent tool in Claude Code), each one fires SubagentStop when it finishes. Use this to coordinate between parallel workstreams or collect results:
{
"hooks": {
"SubagentStop": [
{
"hooks": [
{
"type": "command",
"command": "echo \"[$(date -Iseconds)] Subagent finished\" >> ~/.claude/subagent-log.txt"
}
]
}
]
}
}
Notification — Catch System Events
Fires when Claude Code sends desktop notifications. Useful for forwarding to custom notification systems:
{
"hooks": {
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code needs attention\" with title \"Claude Code\"' 2>/dev/null || true"
}
]
}
]
}
}
5 Practical Hook Setups
1. Auto-Format After Every Edit
Never manually run Prettier again. Format every file as soon as Claude writes it:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "FILE=$(echo \"$CLAUDE_TOOL_INPUT\" | python3 -c \"import json,sys; d=json.load(sys.stdin); print(d.get('file_path',''))\" 2>/dev/null); [ -n \"$FILE\" ] && command -v prettier >/dev/null && prettier --write \"$FILE\" 2>/dev/null || true"
}
]
}
]
}
}
2. Slack Notification on Long Task Completion
Hooks shine brightest during autonomous agent runs — when Claude is completing a 15-minute task unattended, you want to know when it’s done. Get notified on your phone when an autonomous build finishes:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "curl -s -X POST \"$SLACK_WEBHOOK_URL\" -H 'Content-type: application/json' -d '{\"text\":\"Claude Code task finished on '\"$(hostname)\"'\"}' 2>/dev/null || true"
}
]
}
]
}
}
Set SLACK_WEBHOOK_URL in your shell profile. Claude Code inherits environment variables from your shell.
3. Auto-Commit After Every File Write
Build a granular git history of everything Claude touches:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "cd $(git rev-parse --show-toplevel 2>/dev/null || echo '.') && git add -A && git diff --cached --quiet || git commit -m 'chore: claude auto-save [skip ci]' --no-verify 2>/dev/null || true"
}
]
}
]
}
}
Every change gets committed automatically. You’ll never lose work from a crashed session again.
4. Run Type-Checking After TypeScript Edits
Catch type errors immediately rather than at the end of a long task:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "FILE=$(echo \"$CLAUDE_TOOL_INPUT\" | python3 -c \"import json,sys; d=json.load(sys.stdin); print(d.get('file_path',''))\" 2>/dev/null); echo \"$FILE\" | grep -q '\\.ts' && npx tsc --noEmit 2>&1 | tail -5 || true"
}
]
}
]
}
}
5. Security Audit on Bash Commands
Log every shell command Claude runs for a post-session security review:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "CMD=$(echo \"$CLAUDE_TOOL_INPUT\" | python3 -c \"import json,sys; d=json.load(sys.stdin); print(d.get('command',''))\" 2>/dev/null); echo \"[$(date -Iseconds)] $CMD\" >> ~/.claude/bash-history.log"
}
]
}
]
}
}
Review ~/.claude/bash-history.log after any autonomous session to see exactly what Claude executed.
Combining Hooks with Claude Code Skills
Hooks become even more powerful when combined with Claude Code skills. A skill defines what Claude does — a hook defines what happens immediately after.
For example, if you have a /deploy skill that pushes changes to staging, a Stop hook can automatically run smoke tests against the staging URL after the deploy completes. The skill focuses on the deployment logic; the hook handles the verification.
Similarly, a /security-audit skill that generates a report can trigger a PostToolUse hook that exports the results to a ticket in Linear or Jira.
This is the pattern that separates a casual Claude Code user from someone running production-grade automations: skills handle the intelligence, hooks handle the integration.
Environment Variables Available to Hooks
When a hook fires, Claude Code sets environment variables your script can read:
| Variable | Contains |
|---|---|
CLAUDE_TOOL_NAME | Name of the tool being called |
CLAUDE_TOOL_INPUT | JSON of tool inputs |
CLAUDE_TOOL_OUTPUT | JSON of tool output (PostToolUse only) |
CLAUDE_SESSION_ID | Current Claude Code session ID |
Use $CLAUDE_TOOL_NAME to write a single hook that branches by tool:
#!/bin/bash
case "$CLAUDE_TOOL_NAME" in
Write|Edit)
echo "File modified" >> ~/.claude/file-log.txt
;;
Bash)
echo "Shell command" >> ~/.claude/bash-log.txt
;;
esac
Save this as ~/.claude/hook-dispatcher.sh, chmod +x it, and reference it in your hooks config.
Keeping Hooks Fast
Hooks pair well with Claude Code’s memory system — use hooks to log what happened, and memory files to carry context across sessions. A PostToolUse hook can append to a session log that your memory system reads next time.
Hooks block the Claude Code event loop if they take too long. Keep them under 1-2 seconds for smooth operation:
- Background long-running tasks with
&and capture the PID - Use
|| trueat the end of every hook command so a hook failure doesn’t kill the session - Test hooks interactively before adding them to settings.json
- Use
2>/dev/nullto suppress noisy error output
Getting Started
The quickest way to start is with a simple activity log:
{
"hooks": {
"PostToolUse": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "echo \"[$(date -Iseconds)] $CLAUDE_TOOL_NAME\" >> ~/.claude/activity.log"
}
]
}
]
}
}
Add this to ~/.claude/settings.json, run a Claude Code session, then cat ~/.claude/activity.log to see exactly what Claude did. From there, you can filter by tool, add conditionals, and build up to the more sophisticated setups above. And if you want Claude’s behavior within sessions to be consistent — not just what happens after — that’s where CLAUDE.md and slash commands come in.
Hooks are one of those features that seem minor at first — until you’ve had Claude automatically run your test suite, commit your changes, and notify you on Slack while you were making coffee. At that point they become something you can’t imagine working without.
For even more automation building blocks, the Claude Skills 360 bundle includes 2,350+ pre-built skills, 45 autonomous agents, and 12 multi-agent swarms — all designed for exactly this kind of production-grade automation. The free starter kit comes with 360 skills and installs in 90 seconds.