Remote OpenClaw Blog
Claude Code Hooks: Setup, Events, and Real Examples
10 min read ·
Claude Code hooks are user-defined shell commands that execute automatically at specific points in Claude Code's lifecycle, such as before a tool runs, after a file edit, or when Claude finishes responding. You configure them in a hooks block inside a settings file like ~/.claude/settings.json or your project's .claude/settings.json, and they give you deterministic control: a formatter or guard rail that always runs, instead of hoping the model remembers to run it.
What Are Claude Code Hooks?
A Claude Code hook is a command that Claude Code itself executes when a lifecycle event fires, without the model deciding anything. The official hooks guide describes them as "user-defined shell commands that execute at specific points in Claude Code's lifecycle," providing "deterministic control over Claude Code's behavior, ensuring certain actions always happen rather than relying on the LLM to choose to run them."
That determinism is the whole point. Telling Claude "always run Prettier after editing" works most of the time. A PostToolUse hook that pipes the edited file path into Prettier works every time. The same logic applies to guard rails: a PreToolUse hook that exits with code 2 when Claude tries to touch .env blocks the edit at the harness level, before the model's judgment ever enters the picture.
Beyond plain shell commands, Claude Code supports four other handler types: http hooks that POST the event payload to an endpoint, mcp_tool hooks that call a tool on a connected MCP server, prompt hooks that ask a fast Claude model for a yes/no decision, and experimental agent hooks that spawn a subagent with read tools to verify a condition. If you are new to the tool itself, start with our Claude Code guide and come back once you have a workflow worth automating.
Where Hooks Are Configured
Hooks are configured under a top-level hooks key in a Claude Code settings file, and the file you choose determines the scope. Per the official hooks reference, these are the locations Claude Code reads:
| Location | Scope | Shareable |
|---|---|---|
~/.claude/settings.json | All your projects | No, local to your machine |
.claude/settings.json | Single project | Yes, can be committed to the repo |
.claude/settings.local.json | Single project | No, gitignored |
| Managed policy settings | Organization-wide | Admin-controlled |
Plugin hooks/hooks.json | While the plugin is enabled | Yes, bundled with the plugin |
| Skill or agent frontmatter | While that component is active | Yes, defined in the component file |
The schema is the same everywhere: each event name maps to an array of matcher groups, and each group holds an array of hook handlers. A minimal real config looks like this:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}
The matcher filters when the group fires. For tool events it matches tool names: Bash, Edit|Write, or a regex like mcp__.* for MCP tools. An empty matcher, "*", or omitting the field matches everything. Run /hooks inside a session to browse every configured hook grouped by event, with labels showing whether each came from user, project, local, plugin, or managed settings. To disable everything temporarily, set "disableAllHooks": true in settings.
Hook Events and Exit Codes
Claude Code fires hook events across the entire session lifecycle, and as of mid-2026 the official reference documents more than 25 of them. Most day-to-day automation uses fewer than ten. Here are the ones that matter most, with whether an exit code 2 can block the action:
| Event | Fires when | Exit code 2 blocks? |
|---|---|---|
PreToolUse | Before a tool call executes | Yes, blocks the tool call |
PostToolUse | After a tool call succeeds | No, but stderr is shown to Claude |
PermissionRequest | When a permission dialog is about to appear | Yes, denies the permission |
UserPromptSubmit | When you submit a prompt, before Claude processes it | Yes, blocks and erases the prompt |
Notification | When Claude Code sends a notification, e.g. waiting for input | No |
Stop | When Claude finishes responding | Yes, forces Claude to keep working |
SubagentStop | When a subagent finishes | Yes |
SessionStart | When a session begins or resumes (matchers: startup, resume, clear, compact) | No, stdout is added as context |
PreCompact | Before context compaction | Yes, blocks compaction |
ConfigChange | When a configuration file changes mid-session | Yes, blocks the change |
The exit code contract is simple. Exit 0 means success, and for UserPromptSubmit and SessionStart stdout gets added to Claude's context. Exit 2 is a blocking error: stderr is fed to Claude and the action is stopped, with the exact effect depending on the event. Any other exit code is a non-blocking error that gets logged while execution continues. Hooks can also return structured JSON on stdout for finer control, such as permissionDecision: "allow" | "deny" | "ask" on PreToolUse. Default timeouts are 600 seconds for command, HTTP, and MCP tool hooks, 30 seconds for prompt hooks, and 60 seconds for agent hooks.
6 Practical Hook Examples
The fastest way to understand hooks is to copy a working one, and every example below is adapted directly from the official documentation. Note that most of these use jq to parse the JSON payload each hook receives on stdin.
1. Desktop notification when Claude needs input
A Notification hook on macOS, so you can switch apps while Claude works:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
}
]
}
]
}
}
On Linux swap the command for notify-send 'Claude Code' 'Claude Code needs your attention'. Set the matcher to permission_prompt or idle_prompt to fire only on those notification types.
2. Auto-format every edited file
The PostToolUse config shown in the schema section above runs Prettier on every file Claude edits or writes. Swap in ruff format, gofmt, or cargo fmt for other stacks.
3. Block edits to protected files
A PreToolUse guard rail script saved to .claude/hooks/protect-files.sh (make it executable with chmod +x):
#!/bin/bash
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 it under PreToolUse with an Edit|Write matcher and the command "$CLAUDE_PROJECT_DIR"/.claude/hooks/protect-files.sh. Exit code 2 blocks the edit and the stderr message tells Claude why, so it adjusts instead of retrying blindly.
4. Re-inject context after compaction
A SessionStart hook with the compact matcher runs after every context compaction, and whatever it prints to stdout is added back into Claude's context. An echo of your project's non-negotiables ("use Bun, not npm; run bun test before committing") survives every compaction, and you can replace the echo with something dynamic like git log --oneline -5.
5. Auto-approve a specific permission prompt
A PermissionRequest hook with an ExitPlanMode matcher that echoes {"hookSpecificOutput": {"hookEventName": "PermissionRequest", "decision": {"behavior": "allow"}}} answers the plan-approval dialog on your behalf. The official docs stress keeping this matcher narrow: an empty matcher would auto-approve every prompt, including shell commands.
6. A prompt-based Stop hook that keeps Claude working
Instead of a shell script, type: "prompt" asks a fast Claude model to judge completion:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Check if all tasks are complete. If not, respond with {\"ok\": false, \"reason\": \"what remains to be done\"}."
}
]
}
]
}
}
When the model returns "ok": false, Claude keeps working and receives the reason as its next instruction. This is the pattern behind most "make Claude finish the job" setups people share, done with supported primitives.
Hooks in Plugins
Plugins ship hooks in a hooks/hooks.json file at the plugin root, and those hooks activate automatically whenever the plugin is enabled. This is how teams distribute guard rails and formatters as one installable unit instead of asking everyone to paste JSON into their settings: the same schema, packaged with a manifest, installed via /plugin install name@marketplace. Inside plugin hooks, the ${CLAUDE_PLUGIN_ROOT} path placeholder points at the plugin's installation directory, so bundled scripts resolve regardless of where the plugin lands on disk.
Skills and subagents can also declare hooks in their frontmatter that apply only while that component is active, including a once: true option for skill hooks that should run a single time per session. For how plugins, skills, and marketplaces fit together, see our Claude plugins guide, and for the command surface that hooks often automate around, the Claude Code commands cheat sheet.
Security Caveats and Limitations
Hooks execute arbitrary commands on your machine with your full user permissions, automatically, and Anthropic's documentation is blunt that you are responsible for what they do. Before registering any hook you did not write, read it. That goes double for plugin hooks, which arrive from third-party marketplaces; enterprise admins can set allowManagedHooksOnly in managed settings to block user, project, and plugin hooks entirely.
Three operational limits are worth knowing. First, PostToolUse hooks cannot undo anything, because the tool already ran; prevention belongs in PreToolUse. Second, Stop hooks fire whenever Claude finishes responding, not only at task completion, so a badly written blocking Stop hook can trap Claude in a loop. Third, hooks in the same event run in parallel, so if two PreToolUse hooks both rewrite a tool's input, the last one to finish wins non-deterministically.
One property cuts the other way and is genuinely useful: a PreToolUse hook that returns permissionDecision: "deny" fires before any permission-mode check, so it blocks the tool even in bypassPermissions mode. Hooks can tighten policy that even YOLO mode cannot loosen, which makes them the strongest guard rail Claude Code offers short of OS-level sandboxing. For broader workflow hygiene around all of this, see our Claude Code best practices guide.
Related Guides
- Claude Code Commands: The Complete Cheat Sheet (2026)
- Claude Plugins: What They Are and How to Install Them
- Claude Code Best Practices in 2026
- Claude Code Guide: CLI, Desktop, Web, and Real-World Fit
Go deeper
The operator playbooks
Production-ready PDF guides for OpenClaw and Hermes Agent — $19.99 each.
Skills for this topic
Browse all skills →Frequently Asked Questions
What are hooks in Claude Code?
Hooks are user-defined shell commands (or HTTP calls, MCP tool calls, prompts, or agents) that Claude Code executes automatically at lifecycle events such as PreToolUse , PostToolUse , Notification , and Stop . They guarantee an action always happens, like formatting after edits or blocking dangerous commands, instead of relying on the model to remember.
Where is the Claude Code hooks config file?
Hooks go in the hooks key of a settings file: ~/.claude/settings.json for all projects, .claude/settings.json for a shared project config, or .claude/settings.local.json for a personal per-project config. Plugins bundle theirs in hooks/hooks.json . Run /hooks in a session to see everything currently registered.
How do I block Claude Code from editing certain files?
Register a PreToolUse hook with an Edit|Write matcher that reads the JSON payload from stdin, checks .tool_input.file_path against your protected patterns, and exits with code 2 to block. The stderr message is fed back to Claude so it knows why the edit was refused.
Can hooks run even in bypassPermissions mode?
Yes. PreToolUse hooks fire before any permission-mode check, so a hook that returns a deny decision blocks the tool call even when Claude Code runs with --dangerously-skip-permissions . Hooks can tighten restrictions but never loosen them past what your permission rules allow.
Are Claude Code hooks safe?
Hooks are as safe as the commands you put in them. They run automatically with your full user permissions, so review every hook before registering it, keep matchers narrow, and treat third-party plugin hooks like any software you install. Organizations can restrict hooks to vetted ones with the allowManagedHooksOnly managed setting.

