Claude Code Hooks: Automate Your AI Coding Workflow
Claude Code hooks run shell commands automatically at specific points in the agent's lifecycle — auto-formatting files, blocking dangerous commands, sending notifications, and enforcing project rules without manual intervention.
Every time Claude Code edits a file, you run Prettier. Every time it finishes a task, you glance at the terminal to see if it needs input. Every time it writes a Bash command, you scan it for anything destructive before approving.
These are all manual steps that should not be manual. Claude Code hooks exist to automate exactly this kind of repetitive, deterministic work — running shell commands at specific points in the agent's lifecycle without you lifting a finger. If you are using Claude Code without hooks, you are doing work the machine should be doing for you.
What Hooks Are
Hooks are user-defined shell commands that execute automatically when specific events occur during a Claude Code session. They are not prompts or suggestions — they are deterministic. When the event fires, the command runs. Every time, without exception.
Think of them like Git hooks or CI pipeline steps. You define a trigger (an event), an optional filter (a matcher), and an action (a shell command). Claude Code handles the rest.
The system supports 14 lifecycle events, from session startup to session end. The most useful ones for day-to-day development are:
- PreToolUse — fires before a tool call executes. Can block it.
- PostToolUse — fires after a tool call succeeds.
- Notification — fires when Claude needs your attention.
- Stop — fires when Claude finishes responding.
- SessionStart — fires when a session begins, resumes, or clears.
Each hook receives JSON context about the event on stdin — the tool name, the command being run, the file being edited — and communicates back through exit codes and stdout. Exit 0 means proceed. Exit 2 means block the action. Print JSON for finer-grained control.
Setting Up Your First Hook
The quickest way to create a hook is through the interactive menu. Type /hooks in the Claude Code CLI and you will see every available event listed. Select one, configure a matcher, and add your command.
But the more maintainable approach is to define hooks in your settings JSON directly. Hooks can live in three places:
| Location | Scope | Shareable |
|---|---|---|
~/.claude/settings.json | All your projects | No — local to your machine |
.claude/settings.json | Single project | Yes — commit to the repo |
.claude/settings.local.json | Single project | No — automatically gitignored |
For hooks that apply everywhere (like notifications), use your user settings. For project-specific hooks (like formatters or linters), use the project settings so your whole team benefits.
Here is the basic structure:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "your-command-here"
}
]
}
]
}
}
The matcher is a regex that filters when the hook fires. "Edit|Write" matches either tool. "Bash" matches only Bash commands. Omit the matcher entirely to fire on every occurrence of the event.
Five Hooks Worth Setting Up Today
1. Desktop Notifications
This is the single most useful hook, and it takes thirty seconds to configure. Instead of watching your terminal for permission prompts or idle messages, get a native desktop notification whenever Claude needs your attention.
macOS:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
}
]
}
]
}
}
Linux:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "notify-send 'Claude Code' 'Claude Code needs your attention'"
}
]
}
]
}
}
Add this to ~/.claude/settings.json so it applies to every project. You will never miss a permission prompt again.
2. Auto-Format on Every Edit
Claude writes functional code, but it does not always match your project's formatting conventions. This hook runs Prettier on every file Claude edits or creates:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}
The hook reads the file path from the JSON input using jq, then passes it to Prettier. Add this to .claude/settings.json in your project root. Your team's code style stays consistent regardless of which developer — human or AI — writes the code.
You can substitute any formatter: black for Python, gofmt for Go, rubocop -A for Ruby. The pattern is the same.
3. Block Dangerous Commands
Claude is generally cautious, but a PreToolUse hook gives you a hard guarantee. This script blocks any Bash command containing rm -rf:
#!/bin/bash
# .claude/hooks/block-destructive.sh
COMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q 'rm -rf'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive command blocked by hook"
}
}'
else
exit 0
fi
Register it in your project settings:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-destructive.sh"
}
]
}
]
}
}
When Claude tries to run rm -rf /tmp/build, the hook intercepts it, returns a deny decision, and Claude receives the reason as feedback. It adapts without you needing to intervene.
You can extend this pattern to block access to production databases, prevent modifications to .env files, or enforce any other safety rule your team needs.
4. Protect Sensitive Files
Similar to blocking commands, you can prevent Claude from editing files that should not be touched:
#!/bin/bash
# .claude/hooks/protect-files.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/")
for pattern in "${PROTECTED_PATTERNS[@]}"; do
if [[ "$FILE_PATH" == *"$pattern"* ]]; then
echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
exit 2
fi
done
exit 0
Register this as a PreToolUse hook with the matcher "Edit|Write". Claude will be told why the edit was blocked and can adjust its approach — perhaps suggesting the change for you to make manually instead.
5. Log Every Command
For audit trails or simply understanding what Claude did during a session, log every Bash command to a file:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.command' >> ~/.claude/command-log.txt"
}
]
}
]
}
}
This is particularly useful when onboarding new team members to AI-assisted development. They can review what Claude actually ran, building confidence in the tool and catching any unexpected patterns early.
Advanced Patterns
Re-inject Context After Compaction
When Claude's context window fills up and compaction occurs, important details can be lost. A SessionStart hook with a compact matcher re-injects critical information every time this happens:
{
"hooks": {
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "echo 'Reminder: use pnpm, not npm. Run pnpm test before committing. Current sprint: auth refactor.'"
}
]
}
]
}
}
Any text your command writes to stdout is added to Claude's context. You could replace the echo with a script that runs git log --oneline -5 to show recent commits, or reads a status file maintained by your CI pipeline.
Async Hooks for Long-Running Tasks
Some hooks take time — running a full test suite, for instance. By default, hooks block Claude's execution until they complete. Setting "async": true runs the hook in the background so Claude keeps working:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/run-tests-async.sh",
"async": true,
"timeout": 300
}
]
}
]
}
}
When the tests finish, the results are delivered to Claude on the next conversation turn. If tests fail, Claude sees the failure output and can act on it. If they pass, Claude gets a confirmation message. All without blocking the current task.
Prompt and Agent Hooks
Not every decision can be reduced to a regex match or a shell script. For cases requiring judgement, Claude Code supports two additional hook types.
Prompt hooks ("type": "prompt") send the hook input to a fast Claude model for single-turn evaluation. The model returns a yes/no decision. This is useful for checks that need natural language understanding — like verifying that all requested tasks are complete before allowing Claude to stop:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Check if all tasks mentioned in the conversation are complete. If not, respond with {\"ok\": false, \"reason\": \"what remains to be done\"}."
}
]
}
]
}
}
Agent hooks ("type": "agent") go further, spawning a subagent that can read files, search code, and use other tools to verify conditions. Use these when the check requires inspecting the actual codebase — like confirming that tests pass before a task is marked complete.
Sharing Hooks With Your Team
Because hooks defined in .claude/settings.json can be committed to your repository, they become shared team infrastructure. This is where hooks deliver the most value at scale.
Consider establishing a standard set of project hooks that every developer gets automatically:
- Auto-formatting on edit (consistent code style)
- Protected file patterns (no accidental
.envmodifications) - Command logging (audit trail for AI-assisted changes)
- Post-compaction context injection (project conventions survive context resets)
New team members who clone the repo immediately get these guardrails. No setup required, no configuration to remember.
For hooks you want across all projects but that are personal (like notification preferences), use ~/.claude/settings.json. For hooks that are project-specific but should not be shared (like paths to local tools), use .claude/settings.local.json.
Debugging Hooks
When a hook is not behaving as expected, start with these steps:
Check the /hooks menu. Type /hooks in the CLI to confirm your hook appears under the correct event.
Toggle verbose mode. Press Ctrl+O during a session to see hook output in the transcript, including exit codes and any stderr messages.
Run with --debug. Start Claude Code with claude --debug for full execution details — which hooks matched, their exit codes, and output.
Test manually. Pipe sample JSON to your script and check the exit code:
echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./my-hook.sh
echo $?
The most common issue is shell profiles that print text on startup (like welcome messages in .zshrc). This output gets prepended to your hook's JSON and breaks parsing. Wrap any echo statements in your shell profile with an interactive check:
if [[ $- == *i* ]]; then
echo "Shell ready"
fi
Key Takeaways
- Hooks run deterministically. Unlike prompts, they are not suggestions — they execute every time the event fires. Use them for anything that must always happen.
- Start with notifications. The highest-impact, lowest-effort hook is a desktop notification for the
Notificationevent. Set it up in your user settings and never miss a prompt again. - Auto-format everything. A
PostToolUsehook onEdit|Writethat runs your formatter keeps code style consistent without thinking about it. - Protect what matters.
PreToolUsehooks can block destructive commands, prevent edits to sensitive files, and enforce safety rules with hard guarantees. - Share via version control. Define hooks in
.claude/settings.jsonand commit them. Your entire team gets the same guardrails automatically. - Use async for slow tasks. Set
"async": trueon hooks that run test suites or deployments so Claude keeps working while they complete.
Hooks turn Claude Code from a tool you supervise into one that supervises itself. The five minutes you spend configuring them will save you hours of manual formatting, vigilant terminal-watching, and post-hoc cleanup.
References
- Hooks Reference — Claude Code Documentation — Anthropic
- Automate Workflows with Hooks — Claude Code Documentation — Anthropic
- Claude Code Hooks: A Practical Guide to Workflow Automation — DataCamp
- A Complete Guide to Hooks in Claude Code — Eesel
- Claude Code Hooks for Production Quality: Setup, Async, and CI/CD Patterns — Pixelmojo
- Claude Code Hooks: Complete Guide to All 12 Lifecycle Events — Claudefast
Related insights
View allStop Hoarding Context: Why Fresh Sessions Make AI Coding Agents Better
Long AI coding sessions degrade output quality as the context window fills up. A practical guide to session management, the /clear command, and using CLAUDE.md to maintain persistent memory across fresh sessions.
Prompts for Vibe Coders: A Practical Guide to Claude Code
The difference between vibe coding frustration and vibe coding success is knowing what to prompt. A practical guide to CLAUDE.md setup, effective prompt patterns, and the Frontend Design skill for non-technical builders using Claude Code.