Most developers use Claude Code the same way they use a chat app. They type a prompt, read the response, and manually decide what to do next. That approach works, but it leaves most of Claude Code's power sitting idle.
Claude Code hooks are the feature that changes this entirely. Hooks are shell commands that fire automatically at specific moments in Claude Code's lifecycle: before a tool runs, after a file is written, when Claude finishes a task, or when a notification is triggered. They require no prompt, no manual intervention, and no AI judgment. They simply execute, every single time, without exception.
Related Course on Vibe Coding Academy

That determinism is the point. According to a February 2026 survey of 15,000 developers by The Pragmatic Engineer, 73% of engineering teams now use AI coding tools daily. But adoption alone does not translate into consistent output quality. Hooks are the mechanism that closes the gap between "Claude usually formats the code correctly" and "the code is always formatted, without exception."
This guide covers everything: what hooks are, all five core hook types, how to configure them, and ten ready-to-copy recipes you can drop into your project today.
Key Takeaways
- Claude Code hooks are deterministic shell commands that fire automatically at specific lifecycle events, unlike CLAUDE.md instructions, they execute every time without exception.
- The five hook types (PreToolUse, PostToolUse, Notification, Stop, SubagentStop) cover the full lifecycle of an AI coding session, from intercepting dangerous commands to auto-running tests.
- Hooks are configured in .claude/settings.json files at three scope levels: global, project-shared, and project-local, making them easy to standardize across teams.
- Exit code 2 in PreToolUse hooks blocks tool execution and sends feedback to Claude, enabling safety guards that prevent destructive commands like rm -rf or DROP TABLE.
- Hooks and MCP servers serve complementary roles: MCP servers add new capabilities to Claude, while hooks enforce behavior guarantees on actions Claude already takes.
- Combining multiple hook recipes (auto-format, lint, test, notify) creates a fully automated CI-like pipeline that runs inside every Claude Code session.
Learn this hands-on
Master Claude Code from hooks to subagents — follow the full course to ship code faster and build AI agents. Check out the How to Master Claude Code: Ship Code Faster & Build AI Agents.

What Are Claude Code Hooks?
Think of hooks as the invisible assistant that runs in the background while Claude Code works. Every time Claude uses a tool, editing a file, running a shell command, fetching data, hooks can intercept that action, react to it, or block it entirely.
Here is the key difference between hooks and a CLAUDE.md instruction file:
- A CLAUDE.md instruction like "always run Prettier after editing files" is a suggestion. Claude interprets it, sometimes follows it, and occasionally forgets it when the context window fills up.
- A PostToolUse hook that runs
prettier --writeafter every Edit is a guarantee. It does not care what Claude was thinking. It runs.
This distinction matters enormously in production workflows. Hooks are not prompts, they are pipeline steps.
As Vercel CEO Guillermo Rauch puts it, "The best developer tools don't just make individual tasks faster, they make entire workflows disappear. The future belongs to teams that automate the mundane and focus human creativity where it matters most." Claude Code hooks embody this philosophy, they automate the repetitive quality checks so developers can focus on building features.
Where Hooks Live
Hooks are configured in JSON settings files. You have three options:
| File | Scope |
|---|---|
~/.claude/settings.json | All projects on your machine |
.claude/settings.json | This project only (commit to share with team) |
.claude/settings.local.json | This project only (never committed) |
The basic configuration structure looks like this:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "prettier --write \"$CLAUDE_TOOL_INPUT_FILE_PATH\""
}
]
}
]
}
}
Each hook entry has three parts:
- The event (e.g.,
PostToolUse), when the hook fires - The matcher, a regex that filters which tools or contexts trigger it
- The command, the shell command or script to execute
The 5 Core Hook Types
Claude Code supports five hook events that cover the full lifecycle of an AI coding session. Understanding each one is the foundation for building a reliable automated workflow.
1. PreToolUse
PreToolUse fires before Claude executes any tool. This is your opportunity to intercept, modify, or block an action before it happens.
Common use cases:
- Block dangerous shell commands (
rm -rf,DROP TABLE,git push --force) - Validate that Claude is editing files only within approved directories
- Auto-create a git commit as a safety checkpoint before a large refactor
Exit codes control what happens next:
- Exit
0, proceed normally - Exit
2, block the tool call and send your stderr message back to Claude - Any other code, non-blocking error, execution continues
2. PostToolUse
PostToolUse fires after a tool has successfully executed. The hook receives both the tool's input and its output, making this the right place for quality enforcement and side effects.
Common use cases:
- Auto-format files after every edit
- Run linting on modified files
- Trigger tests when source files change
- Log every file change to an audit trail
3. Notification
The Notification hook fires when Claude Code sends a notification, most commonly when it is waiting for permission to run a command. Instead of checking your terminal every 30 seconds, you can pipe these notifications to Slack, a desktop alert, or a custom webhook.
Common use cases:
- Send Slack messages when Claude is blocked and waiting for you
- Log permission requests to a central team channel
- Trigger a phone notification when a long-running task completes
4. Stop
The Stop hook fires when Claude Code finishes a response and is ready for your next input. This is the "end of turn" event.
Common use cases:
- Run a full test suite after Claude completes a feature
- Automatically commit completed work to a staging branch
- Send a summary notification when Claude finishes a long task
The Stop hook can return "decision": "block" with a reason, which forces Claude to continue working rather than stopping. This is how you implement an "auto-continue if tests fail" pattern.
5. SubagentStop
SubagentStop is the same as Stop, but fires specifically when a subagent (spawned via the Task tool) finishes. If Claude Code creates subagents to parallelize work, each subagent's completion fires a SubagentStop event independently.
Common use cases:
- Validate each subagent's output before the parent agent proceeds
- Aggregate results from parallel subagents into a single log
- Block a subagent from completing if its output fails a quality check
Step-by-Step Setup Guide
Here is how to configure your first hook from scratch. This example sets up auto-formatting on every file edit.
Step 1: Open or create your settings file
Navigate to your project root and open (or create) .claude/settings.json:
mkdir -p .claude && touch .claude/settings.json
Step 2: Add the hook configuration
Paste the following into the file:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "prettier --write \"$CLAUDE_TOOL_INPUT_FILE_PATH\""
}
]
}
]
}
}
Step 3: Verify the hook is active
Type /hooks inside Claude Code to open the read-only hook browser. You will see your configured hook listed with its event type, matcher, and command. If it appears here, it is active.
Step 4: Test it
Ask Claude Code to edit any file in your project. After the edit completes, check the file, it should be formatted by Prettier automatically, with no prompt or manual action from you.
That is the entire setup process. Every hook recipe in the next section follows the same pattern.
10 Ready-to-Copy Hook Recipes
These recipes cover the most common automation patterns. Copy them directly into your .claude/settings.json, adjusting paths and commands for your project.
Recipe 1: Auto-Format on Save (Prettier)
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "prettier --write \"$CLAUDE_TOOL_INPUT_FILE_PATH\""
}
]
}
]
}
}
Fires after every file write or edit. Keeps all AI-generated code consistently formatted with zero manual intervention.
Recipe 2: ESLint Check After File Edits
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "eslint --fix \"$CLAUDE_TOOL_INPUT_FILE_PATH\" 2>&1 || true"
}
]
}
]
}
}
Runs ESLint with auto-fix after every edit. The || true prevents the hook from blocking Claude if ESLint finds unfixable issues, it corrects what it can and moves on.
Recipe 3: Block Dangerous Shell Commands
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo \"$CLAUDE_TOOL_INPUT\" | python3 -c \"\nimport sys, json\ncmd = json.load(sys.stdin).get('command', '')\ndangerous = ['rm -rf /', 'DROP TABLE', 'git push --force']\nif any(d in cmd for d in dangerous):\n print(f'BLOCKED: {cmd}', file=sys.stderr)\n sys.exit(2)\n\""
}
]
}
]
}
}
Intercepts every Bash tool call and checks it against a list of dangerous patterns. Exit code 2 blocks execution and sends the reason back to Claude.
Recipe 4: Auto-Commit After File Edits
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "cd \"$CLAUDE_PROJECT_DIR\" && git add -A && git commit -m \"auto: claude edit $(date +%H:%M:%S)\" --no-verify 2>/dev/null || true",
"async": true
}
]
}
]
}
}
Creates an automatic git commit after every file change. The async: true flag means this runs in the background without slowing down Claude's workflow.
Recipe 5: Run Tests After Stop
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "cd \"$CLAUDE_PROJECT_DIR\" && npm test 2>&1 | tail -20"
}
]
}
]
}
}
Runs your full test suite every time Claude finishes a response. The output is added to Claude's context, so if tests fail, Claude can see the failure and fix it in the next turn.
Recipe 6: Slack Notification When Claude Needs You
{
"hooks": {
"Notification": [
{
"matcher": "permission_prompt",
"hooks": [
{
"type": "command",
"command": "curl -s -X POST \"$SLACK_WEBHOOK_URL\" -H 'Content-type: application/json' -d '{\"text\":\"Claude Code is waiting for permission. Check your terminal.\"}' > /dev/null",
"async": true
}
]
}
]
}
}
Any time Claude Code needs permission to run a command, you get a Slack message. Eliminates the need to babysit the terminal during long sessions.
Recipe 7: TypeScript Type-Check After Edits
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "cd \"$CLAUDE_PROJECT_DIR\" && npx tsc --noEmit 2>&1 | head -20 || true"
}
]
}
]
}
}
Runs TypeScript's type checker after every edit and surfaces errors in Claude's context. Claude sees type errors immediately and can correct them before moving on.
Recipe 8: Safety Checkpoint Before Refactors
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit",
"hooks": [
{
"type": "command",
"command": "cd \"$CLAUDE_PROJECT_DIR\" && git diff --stat HEAD | grep -q '.' && git stash push -m \"pre-edit-$(date +%s)\" || true",
"async": true
}
]
}
]
}
}
Automatically stashes uncommitted changes before Claude edits a file. Creates restore points you can return to if a multi-file refactor goes wrong.
Recipe 9: Log All File Changes to an Audit Trail
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "echo \"$(date -u +%Y-%m-%dT%H:%M:%SZ) EDITED: $CLAUDE_TOOL_INPUT_FILE_PATH\" >> \"$CLAUDE_PROJECT_DIR/.claude/audit.log\"",
"async": true
}
]
}
]
}
}
Appends a timestamped log entry for every file Claude modifies. Useful for compliance, debugging, or reviewing what changed during a long session.
Recipe 10: Inject Git Status at Session Start
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "cd \"$CLAUDE_PROJECT_DIR\" && echo '=== Current Git Status ===' && git status --short && echo '=== Recent Commits ===' && git log --oneline -5"
}
]
}
]
}
}
Every time you open a Claude Code session, it automatically injects the current git status and recent commits into the context. Claude starts every session with full awareness of the repository state.
Hooks vs. MCP Servers: What Is the Difference?
Both hooks and MCP servers extend Claude Code's capabilities, but they serve fundamentally different purposes.
MCP servers give Claude Code access to external tools and data sources. They add new capabilities, the ability to query a database, read a Figma file, interact with Slack, or search the web. Claude decides when to use an MCP tool based on your prompts and the task at hand.
Hooks automate reactions to things Claude is already doing. They do not add new capabilities, they enforce behavior guarantees. A hook does not give Claude the ability to run Prettier; it ensures Prettier always runs, regardless of whether Claude thought to do it.
The practical rule of thumb:
- Use an MCP server when you want to give Claude access to something new.
- Use a hook when you want to guarantee something always happens.
They also compose well together. You can use a PreToolUse hook to validate inputs before an MCP tool call fires, or a PostToolUse hook to log the results of an MCP tool call to your audit trail.
Troubleshooting Common Hook Failures
Hook does not appear to be running. Check that your settings.json is valid JSON with no syntax errors. Open the /hooks menu inside Claude Code, if your hook is not listed there, it is either not being parsed or saved in the wrong location. Also confirm that disableAllHooks is not set to true anywhere in your settings hierarchy.
Hook runs but the command fails silently. By default, non-zero exit codes (other than 2) produce non-blocking errors that only appear in verbose mode. Run Claude Code with the --verbose flag to see all hook output. If the command works in your terminal but not in the hook, the issue is usually a missing PATH or environment variable, use absolute paths for all binaries.
Hook blocks Claude unexpectedly. If your PreToolUse hook is returning exit code 2 when you do not want it to, add echo "$CLAUDE_TOOL_INPUT" >> /tmp/hook-debug.log at the start of your script to inspect the raw input Claude is sending.
Formatter hook causes an infinite loop. A PostToolUse hook that writes to a file can theoretically trigger itself. Prevent this by checking whether the triggering write came from Claude versus your hook script before re-running the formatter.
Environment variables not available in hook commands. Hooks run in a clean shell environment. If your script needs environment variables (like SLACK_WEBHOOK_URL), load them from a file at the start of your hook script: source "$CLAUDE_PROJECT_DIR/.env".
Combine Hooks Into a Full Automated Pipeline
The real power of Claude Code hooks emerges when you combine multiple recipes. Here is a production-ready configuration for a TypeScript project:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "cd \"$CLAUDE_PROJECT_DIR\" && git status --short && git log --oneline -5"
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo \"$CLAUDE_TOOL_INPUT\" | python3 /path/to/safety-check.py"
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "prettier --write \"$CLAUDE_TOOL_INPUT_FILE_PATH\" && eslint --fix \"$CLAUDE_TOOL_INPUT_FILE_PATH\" 2>&1 | head -5 || true"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "cd \"$CLAUDE_PROJECT_DIR\" && npm test 2>&1 | tail -10"
}
]
}
],
"Notification": [
{
"matcher": "permission_prompt",
"hooks": [
{
"type": "command",
"command": "curl -s -X POST \"$SLACK_WEBHOOK_URL\" -d '{\"text\":\"Claude needs permission\"}' > /dev/null",
"async": true
}
]
}
]
}
}
With this configuration in place, Claude Code starts every session context-aware, blocks dangerous commands automatically, formats and lints every file it touches, runs tests after every completed response, and pings you on Slack when it needs input.
Start with One Hook
The most common mistake people make with Claude Code hooks is trying to configure everything at once. Start with a single PostToolUse hook that runs Prettier after file edits. Get comfortable with the feedback loop. Then add linting. Then tests. Then notifications.
Each hook you add compounds the value of the one before it. Within a week of consistent use, your Claude Code workflow will be unrecognizable from what it was before, and the quality of AI-generated code in your projects will show it.
If you want to go deeper into automating your entire development workflow with Claude Code, including subagents, MCP servers, and project-level configuration patterns, the full Claude Code course walks through each piece with real projects.
