How to Use Hooks in Claude Code
Hooks let you intercept and extend Claude Code's behavior with custom shell commands. Set up auto-formatting, notifications, security guardrails, and workflow automations that run automatically.
Hooks are custom shell commands that run automatically when specific events happen in Claude Code — like auto-formatting files after every edit, blocking dangerous commands before they execute, or sending a Slack notification when a session ends. You configure them in your .claude/settings.json file using fourteen available event types.
This guide covers how to set up hooks, every available event, and practical examples for auto-formatting, security guardrails, audit logging, and workflow automation you can use today.
What Are Claude Code Hooks?
A hook is a shell command that Claude Code executes automatically when a targeted event occurs. Think of hooks as triggers: when X happens, run Y.
When a hook event fires, Claude Code passes JSON context about the event to your hook command via stdin. Your command can read that context, do something with it, and optionally return instructions back to Claude.
Without hooks: You manually format code after every edit, approve repetitive commands, and have no visibility into what Claude does.
With hooks: Formatting happens automatically, safe commands get auto-approved, and you have complete audit logs of every action.
Available Hook Events
Claude Code provides fourteen hook events that cover the full lifecycle of a session:
| Event | When It Fires |
|---|---|
SessionStart |
When a session begins or resumes (load context, set env vars) |
UserPromptSubmit |
When you submit a prompt (can validate, modify, or block) |
PreToolUse |
Before a tool executes (can block, modify input, or approve) |
PermissionRequest |
When a permission dialog appears (can auto-approve or deny) |
PostToolUse |
After a tool completes successfully (for formatting, logging) |
PostToolUseFailure |
After a tool call fails (for error logging, alerts) |
Notification |
When Claude Code sends a notification (permission, idle, auth) |
SubagentStart |
When a subagent is spawned (inject context) |
SubagentStop |
When a subagent finishes its task |
Stop |
When Claude finishes responding (can force continuation) |
TeammateIdle |
When an agent team teammate is about to go idle |
TaskCompleted |
When a task is being marked as completed (can enforce quality gates) |
PreCompact |
Before context compaction (manual or auto) |
SessionEnd |
When the Claude Code session terminates |
Most common hooks: Most users start with PostToolUse for auto-formatting and PreToolUse for security guardrails. These two handle 80% of use cases.
Where to Configure Hooks
Hooks are configured in your Claude Code settings files using JSON. You have three options:
| File Location | Scope |
|---|---|
~/.claude/settings.json |
User-wide (all projects) |
.claude/settings.json |
Project (shared with team via git) |
.claude/settings.local.json |
Project (personal, gitignored) |
For personal automations, use your user settings. For team conventions, use project settings. Hooks can also be defined in plugins (hooks/hooks.json), in skill or subagent frontmatter, and through managed enterprise policy settings.
You can also use the /hooks slash command to view, add, and delete hooks interactively without editing JSON files directly.
How to Configure Your First Hook
Let's set up a practical hook: auto-formatting code after Claude edits files.
Step 1: Open your settings file
Create or edit ~/.claude/settings.json for user-wide hooks, or .claude/settings.json in your project folder.
Step 2: Add the hooks configuration
Add a hooks object with your event and commands:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "npx prettier --write \"$TOOL_INPUT_FILE_PATH\""
}
]
}
]
}
}
Step 3: Restart Claude Code
Exit and restart Claude Code for the hook to take effect. Now every time Claude edits or writes a file, Prettier runs automatically. You can also use the /hooks slash command to manage hooks interactively without editing JSON files directly.
Understanding the Hook Structure
Each hook configuration has three levels:
- Event: Which lifecycle event to respond to (like
PreToolUseorStop) - Matcher: A regex pattern that filters when the hook fires.
"Edit|Write"matches either tool. Omit the matcher or use"*"to match all occurrences. - Hook handler: What to run when matched. There are three types.
Three Types of Hook Handlers
Claude Code supports three types of hook handlers:
- Command hooks (
"type": "command"): Run a shell command. Your script receives JSON input on stdin and communicates results through exit codes and stdout. This is the most common type. - Prompt hooks (
"type": "prompt"): Send a prompt to a Claude model for single-turn evaluation. The model returns a yes/no decision as JSON. Use when the hook input data alone is enough to make a decision. - Agent hooks (
"type": "agent"): Spawn a subagent that can use tools like Read, Grep, and Glob to verify conditions before returning a decision. Use when verification requires inspecting actual files or test output, with up to 50 tool-use turns.
Most examples in this guide use command hooks. Prompt and agent hooks are powerful when you need Claude's judgment to decide whether to allow or block an action.
Real-World Hook Examples
Auto-Format Code After Edits
Run Prettier (or any formatter) automatically after Claude writes or edits files:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "npx prettier --write \"$TOOL_INPUT_FILE_PATH\""
}
]
}
]
}
}
Block Dangerous Shell Commands
Prevent Claude from running destructive commands like rm -rf:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/.claude/scripts/block-dangerous.sh"
}
]
}
]
}
}
The script reads the command from stdin and exits with code 2 to block it (exit code 2 is the blocking signal in Claude Code hooks):
#!/bin/bash
# ~/.claude/scripts/block-dangerous.sh
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')
if echo "$COMMAND" | grep -qE 'rm -rf|rm -r /|dd if=|mkfs|:(){'; then
echo "Blocked: dangerous command detected" >&2
exit 2
fi
Auto-Approve Safe Commands
Automatically approve commands you trust, like running tests:
{
"hooks": {
"PermissionRequest": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/.claude/scripts/auto-approve.sh"
}
]
}
]
}
}
#!/bin/bash
# ~/.claude/scripts/auto-approve.sh
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')
if echo "$COMMAND" | grep -qE '^(npm test|npm run lint|git status|git diff)'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PermissionRequest",
decision: { behavior: "allow" }
}
}'
exit 0
fi
# Don't auto-approve, let user decide
exit 0
Log All Claude Actions
Create an audit trail of everything Claude does:
{
"hooks": {
"PostToolUse": [
{
"hooks": [
{
"type": "command",
"command": "~/.claude/scripts/log-action.sh",
"async": true
}
]
}
]
}
}
#!/bin/bash
# ~/.claude/scripts/log-action.sh
INPUT=$(cat)
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
echo "[$TIMESTAMP] $INPUT" >> ~/.claude/audit.log
Notice the "async": true flag. This runs the logging hook in the background without blocking Claude's execution, which is ideal for logging and analytics.
Send Notifications When Done
Get notified when Claude finishes a long task:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude finished\" with title \"Claude Code\"'"
}
]
}
]
}
}
Linux notification alternative
On Linux, use notify-send "Claude Code" "Claude finished" instead of osascript.
Modifying Tool Inputs with Hooks
Starting in Claude Code v2.0.10, PreToolUse hooks can modify tool inputs before execution. This is powerful for enforcing conventions transparently.
Example: Automatically add dry-run flags to destructive commands:
#!/bin/bash
# ~/.claude/scripts/add-dry-run.sh
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')
# Add --dry-run to rm commands
if echo "$COMMAND" | grep -q '^rm '; then
MODIFIED=$(echo "$COMMAND" | sed 's/^rm /rm --dry-run /')
echo "{\"modified_input\": {\"command\": \"$MODIFIED\"}}"
exit 0
fi
exit 0
The hook intercepts the command, modifies it, and returns the modified version. Claude proceeds with the safer command without needing to retry.
Async Hooks (Background Execution)
By default, hooks block Claude's execution until they complete. For long-running tasks like test suites, deployments, or external API calls, add "async": true to run the hook in the background while Claude continues working.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "~/.claude/scripts/run-tests.sh",
"async": true,
"timeout": 120
}
]
}
]
}
}
When an async hook finishes, if it returns JSON with a systemMessage or additionalContext field, that content is delivered to Claude on the next conversation turn. Async hooks cannot block or return decisions since the triggering action has already completed.
Best for: Logging, analytics, notifications, running tests, memory storage, and any side-effect that shouldn't slow things down. Only command hooks support async; prompt and agent hooks cannot run asynchronously.
Prompt and Agent Hooks
Beyond shell command hooks, Claude Code supports two LLM-powered hook types that use Claude's judgment to make decisions:
Prompt Hooks
A prompt hook sends the hook input to a Claude model for a single-turn evaluation. The model returns a yes/no decision. Use when the hook input data alone is enough to make a decision.
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Evaluate if Claude should stop: $ARGUMENTS. Check if all tasks are complete and tests pass.",
"timeout": 30
}
]
}
]
}
}
The model responds with {"ok": true} to allow or {"ok": false, "reason": "..."} to block.
Agent Hooks
An agent hook spawns a subagent that can use tools like Read, Grep, and Glob to verify conditions before returning a decision. Use when verification requires inspecting actual files or test output.
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "agent",
"prompt": "Verify all unit tests pass. Run the test suite and check results. $ARGUMENTS",
"timeout": 120
}
]
}
]
}
}
Agent hooks have a longer default timeout (60 seconds vs. 30 for prompt hooks) and can perform up to 50 tool-use turns before returning a decision.
Hooks in Skills and Subagents
Hooks can also be defined directly in skills and subagents using YAML frontmatter. These hooks are scoped to the component's lifecycle and only run when that skill or agent is active:
---
name: secure-operations
description: Perform operations with security checks
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/security-check.sh"
---
For subagents, Stop hooks defined in frontmatter are automatically converted to SubagentStop events.
Common Use Cases
Auto-Formatting
Run Prettier, Black, gofmt, or any formatter after file changes. Keeps code consistent without manual intervention.
Security Guardrails
Block dangerous commands, redact secrets from outputs, enforce sandboxing rules. Prevent accidents before they happen.
Workflow Automation
Run tests after code changes, trigger builds, update documentation. Chain Claude's actions with your existing workflows.
Audit Logging
Log every action Claude takes for compliance, debugging, or learning. Build complete audit trails automatically.
Notifications
Send Slack messages, desktop notifications, or emails when Claude completes tasks or encounters errors.
Team Conventions
Enforce commit message formats, branch naming, code style. Ensure consistency across your whole team.
Security Considerations
Hooks are powerful, which means they require care:
Important: Hooks run with your user permissions. A malicious hook can do anything you can do on your machine. Only use hooks from sources you trust.
- Review hook code: Before installing any hook script, read through it and understand what it does.
- Be careful with auto-approve: Auto-approving commands can be convenient but dangerous. Only auto-approve commands you fully trust.
- Don't store secrets in hooks: If your hook needs API keys, use environment variables, not hardcoded values.
- Test in safe environments: Test new hooks on non-critical projects first.
- Check project hooks: When cloning a new repo, review its
.claude/settings.jsonfor any hooks before running Claude Code.
Debugging Hooks
If a hook isn't working:
- Check the matcher: Make sure your regex pattern matches the tool name. Use
".*"to match all tools while testing. - Test the command manually: Run your hook command directly in the terminal with sample input to verify it works.
- Check file permissions: Make sure your hook script is executable (
chmod +x script.sh). - Look for JSON errors: If your hook returns JSON, make sure it's valid. Use
jqto validate. - Check Claude Code debug output: Run
claude --debugto see hook execution details, including which hooks matched, their exit codes, and output. Toggle verbose mode withCtrl+Oin a session. - Hooks capture at startup: Claude Code captures a snapshot of hooks when a session starts. If you edit hooks in settings files mid-session, Claude Code warns you and requires review in the
/hooksmenu before changes apply.
Common Mistakes
- Forgetting to make scripts executable: Run
chmod +xon your hook scripts. - Invalid JSON in settings: One misplaced comma breaks everything. Use a JSON validator.
- Matcher too broad: A
".*"matcher runs on every tool. Be specific when possible. - Blocking without feedback: If a PreToolUse hook blocks an action, return an error message explaining why.
- Not handling missing fields: Your hook might receive different JSON structures. Check fields exist before using them.
FAQ
Can hooks break Claude Code?
A failing hook won't crash Claude Code, but it might block actions or cause unexpected behavior. Test hooks carefully and check logs if things seem wrong.
Do hooks run for subagents too?
Yes. Hooks fire for tool calls from both the main agent and subagents. The SubagentStop event specifically fires when a subagent completes.
Can I have multiple hooks for the same event?
Yes. Hooks are defined in an array, so you can have multiple hooks for the same event with different matchers. They run in order.
How do I disable a hook temporarily?
Set "disableAllHooks": true in your settings file to disable all hooks at once, or use the toggle in the /hooks menu. To remove a single hook, delete its entry from the JSON file or select it in the /hooks menu and delete it.
Can hooks communicate with each other?
Not directly, but they can read and write to shared files. Use a temp file or a simple database like SQLite to pass data between hooks.
Related Guides
- How to Use CLAUDE.md Files - configure Claude's behavior
- How to Use Subagents - delegate tasks to specialized agents
- Slash Commands Reference - all available commands
Like Claude Code? Meet Your Chief AI Officer
Watch me build a complete website using only plain English - no coding required. Then try it yourself.
Get the Free Blueprint href="/blueprint">Watch the Free Setup Video →rarr;