Claude Code Hooks: Automate Your AI Coding Workflow
Claude Code hooks run shell commands for you at set points while the agent works, so files get formatted, risky commands get blocked, you get a ping when Claude needs you, and your project rules stick without you having to check each step.
If you use Claude Code a lot, you probably run Prettier after it edits a file, glance at the terminal when it finishes to see if it needs you, and read each Bash command it writes for anything destructive before you approve it.
You don't need to do any of that by hand. Claude Code hooks run shell commands at set points in the agent's lifecycle, so this kind of repeat work happens on its own. If you use Claude Code without hooks, you're doing jobs the machine could do for you.
What Hooks Are
Hooks are shell commands you write yourself, and Claude Code runs them automatically when certain events happen in a session. Claude can't choose to skip them, because when the event fires, the command runs every time.
If you've used Git hooks or CI pipeline steps, they'll feel familiar. You set a trigger (an event), an optional filter (a matcher) and an action (a shell command), and Claude Code does 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, such as the tool name, the command being run, and the file being edited, then replies through exit codes and stdout. Exit 0 means carry on, exit 2 means block the action, and you can print JSON if you want more 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'll see a list of every event. Pick one, set a matcher and add your command.
In the long run it's easier to look after your hooks if you write them straight into your settings JSON. 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, and if you leave the matcher out, the hook fires every time the event happens.
Five Hooks Worth Setting Up Today
1. Desktop Notifications
For us this is the most useful hook, and it takes about thirty seconds to set up. You get a normal desktop notification when Claude needs you, so you don't have to keep an eye on the terminal for permission prompts or idle messages.
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 works in every project, and you won't miss a permission prompt again.
2. Auto-Format on Every Edit
Claude's code works, but it doesn't always follow your project's formatting rules. 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, and your team's code style stays the same whether a person or Claude wrote the code.
You can swap in any formatter you like, such as black for Python, gofmt for Go or rubocop -A for Ruby, and the setup works the same way.
3. Block Dangerous Commands
Claude is usually careful, but a PreToolUse hook means you don't have to rely on that. 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 catches it and returns a deny decision, and Claude gets the reason as feedback, so it changes course without you having to step in.
You can use the same idea to block access to production databases, stop changes to .env files, or apply any other safety rule your team needs.
4. Protect Sensitive Files
In much the same way, you can stop Claude from editing files that should be left alone:
#!/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 is told why the edit was blocked and can try something else, for example by suggesting the change for you to make by hand.
5. Log Every Command
If you want an audit trail, or just want to see what Claude did in a session, you can 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 handy when new team members are starting out with AI-assisted development. They can look back at what Claude actually ran, which helps them trust the tool and spot anything odd early on.
Advanced Patterns
Re-inject Context After Compaction
When Claude's context window fills up and compaction happens, it can lose important details. A SessionStart hook with a compact matcher puts the key information back each time:
{
"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 a while, such as a full test suite run, and by default Claude waits for each hook to finish before it carries on. 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, Claude gets the results on the next conversation turn. If they fail, Claude sees the output and can fix things, and if they pass, it gets a confirmation message, all without holding up the current task.
Prompt and Agent Hooks
Some decisions are hard to fit into a regex match or a shell script. For checks that need some judgement, Claude Code has two more hook types.
Prompt hooks ("type": "prompt") send the hook input to a fast Claude model, which looks at it once and returns a yes/no decision. This helps with 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") start a subagent that can read files, search code and use other tools to check things. Use these when the check needs to look at the actual codebase, like confirming that tests pass before a task is marked complete.
Sharing Hooks With Your Team
You can commit hooks in .claude/settings.json to your repository, so the whole team shares them, and for bigger teams that's where they help the most.
It's worth agreeing on a standard set of project hooks that every developer gets automatically, for example:
- 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 get these guardrails as soon as they clone the repo, with nothing to set up or 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
If a hook isn't doing what you expect, try these steps first:
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 problem is a shell profile that prints text on startup (like a welcome message in .zshrc), because that text ends up in front of your hook's JSON and breaks the parsing. Wrap any echo statements in your shell profile with an interactive check:
if [[ $- == *i* ]]; then
echo "Shell ready"
fi
Key Takeaways
- Hooks always run. Claude can choose to ignore a prompt, but a hook runs every time its event fires, so use hooks for anything that must always happen.
- Start with notifications. A desktop notification on the
Notificationevent takes very little effort and helps a lot. Set it up in your user settings and you won't miss a prompt again. - Format on every edit. A
PostToolUsehook onEdit|Writethat runs your formatter keeps your code style the same without you having to think about it. - Protect the important files.
PreToolUsehooks can block destructive commands and stop edits to sensitive files, and Claude can't get round them. - Share them in the repo. Put your hooks in
.claude/settings.jsonand commit them, so the whole 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 finish.
A good place to start is the notification hook in ~/.claude/settings.json, and then the formatter in your project settings when you next open the repo. The five minutes you spend setting them up will save you hours of formatting by hand, watching the terminal and tidying up afterwards.
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 allTeaching Claude Code Your House Style with Agent Skills
Agent Skills let you put your team's conventions, workflows and checklists into a SKILL.md file that Claude Code loads when it needs it. Here's how skills differ from CLAUDE.md, why the description field matters so much, and the real skill we use to run this blog.
Website Accessibility: What the Equality Act Actually Requires
Your website counts as a service under the Equality Act 2010, and you have to make it accessible before anyone complains. UK enforcement is rare, but the lost customers are real, so we cover the £274bn Purple Pound, the four failures behind most broken sites, and a free ten-minute self-check.