First hook
Your first Claude Code hook
A hook is a handler Claude Code runs at a defined point in its lifecycle. You configure it for an event and an optional matcher. Most hooks use type: "command", and command hooks receive event-specific JSON on standard input; Claude Code also supports http, mcp_tool, prompt, and experimental agent handlers. Around tool use, PreToolUse runs before a tool call and can block or modify it, while PostToolUse runs after a successful tool call and can react to its result.
A minimal .claude/settings.json wires one handler to one event. The matcher selects which tool names trigger it, the handler type selects how it runs, and ${CLAUDE_PROJECT_DIR} lets a project hook reference a script from the project root rather than relying on the hook process working directory.
Last verified against the official Claude Code hooks documentation on September 5, 2026. Hook fields evolve; the hooks reference for your installed version is the source of truth.
The events
How the Claude Code hook lifecycle works
Several hook events fire across a session. These are the ones that matter for safety automation:
| Event | When it fires | Can it block? | Typical use |
|---|---|---|---|
SessionStart | When a session begins or resumes | No | Load context, set up the environment |
PreToolUse | Before a tool call executes | Yes | Inspect and block risky calls |
PostToolUse | After a tool call succeeds | No; the tool already ran | Run tests, format, lint |
Stop | Whenever Claude finishes responding | Yes; prevents stopping and continues the conversation | Validate a response before ending the turn |
This guide focuses on PreToolUse and PostToolUse, because they are where policy and automation actually live. The full event list is in the hooks reference.
PreToolUse
Example: block risky bash commands
A command PreToolUse hook receives the pending tool call as JSON on standard input, including tool_name and tool_input. For a Bash call, tool_input.command holds the command string. The hook reads that JSON and reports its decision through its exit code or structured JSON output.
For a command PreToolUse hook, exit code 2 blocks the tool call and Claude receives the text written to standard error as the denial reason. Exit code 0 with no output reports no hook decision, so the call continues through the normal permission flow and is not automatically approved. A guard that refuses force-pushes might scan tool_input.command for the pattern, print a reason to standard error, and exit 2 when it matches; otherwise exit 0. For structured control, exit 0 and print valid JSON to standard output.
Test the hook against a command you know should be blocked before trusting it, and print the reason to standard error rather than standard output so Claude receives it as feedback.
PreToolUse
Example: protect sensitive files
To keep an agent away from secrets and deploy configuration, match PreToolUse on the Edit and Write tools, read the target path from tool_input, normalize it, and exit 2 when it resolves inside a protected location such as .env, a keys directory, or a deploy manifest.
Use a deny rule as the baseline control for the paths you never want touched, and reserve the hook for the logic a static rule cannot express, such as normalizing symlinks or checking a path against a computed allowlist. Rules and hooks are complementary: the rule is the simple floor, the hook is the programmable ceiling.
PostToolUse
Example: run tests after edits
A PostToolUse hook matched on Write|Edit can run a formatter or a fast test after each successful change and return the result to Claude. Because the tool has already run, the hook cannot undo the edit; it can only surface a failure so Claude responds on the next turn.
Keep the distinction between pre-execution gating and post-execution feedback in mind. Running your entire test suite after every edit will slow the session, so scope PostToolUse to a targeted test, a file-level type check, or a formatter. Stop fires whenever Claude finishes responding, not only at task completion, so reserve the full suite for CI unless repeated Stop-time validation is intentional.
Precedence
Hooks and permission rules: which one wins?
Hooks and permission rules are different mechanisms, and their interaction has a few fixed points worth memorizing:
- A blocking
PreToolUsehook can stop a call that permission rules would otherwise have allowed. Your hook is an additional gate, not an override of the permission system. - A hook cannot loosen policy. Explicit deny and ask rules are still evaluated, so a hook returning success does not turn a denied action into an allowed one.
PostToolUsecannot undo. The tool already ran; the hook reacts, it does not reverse.- Order of reasoning matters for debugging: if an action is blocked, check whether a deny rule, an ask rule, or your own hook stopped it before assuming a bug.
Timeouts
Failure modes and timeout behavior
A hook is code, and code fails. The failure behavior is the part people miss: when a command, http, or mcp_tool PreToolUse hook times out, Claude Code discards its output and does not block the tool call, which continues through the normal permission flow. An Agent SDK callback hook behaves differently: if it exceeds its timeout, it blocks the tool call.
The consequence is a design rule: never make a timeout-prone command, HTTP, or MCP-tool hook your only barrier against a dangerous action. Pair programmable checks with explicit deny rules so the floor holds even when the hook does not run, and keep hook handlers fast and dependency-light so they rarely time out in the first place.
Debugging
Testing and debugging hooks
When a hook does not fire or does not behave, the cause is usually mundane. Work down this list:
- The matcher does not match the tool name. Confirm the exact tool name and matcher syntax against the reference.
- The input JSON is parsed wrong. In a throwaway repository, capture a redacted sample or inspect the Claude Code debug log to confirm the actual field names; do not persist raw hook input, because
tool_inputcan contain sensitive commands, paths, or file content. - The script is not executable, or the path is relative and resolves differently than you expect. Prefer an absolute path via
${CLAUDE_PROJECT_DIR}. - Path separators differ on Windows. Do not assume forward slashes in path comparisons.
- The exit code is wrong. For a command
PreToolUsehook, exit code 2 blocks; exit code 0 with no output reports no decision and returns the call to the normal permission flow.
Do this testing in a throwaway repository or a container, not against work you care about, because a hook under development will misfire.
Trust
Security risks of hooks in headless runs
Command hooks run with your full user permissions, so a hook is itself an attack surface. Project hooks can be committed with a repository. In a -p or SDK session, Claude Code does not show the workspace trust dialog and treats the folder as trusted, so hooks in a repository .claude/settings.json can run in a folder you have never trusted. The --worktree path is an exception and still requires trust to have been accepted for the directory.
Before running Claude Code in a repository you did not write, review its .claude/ settings files and every command or script they reference. For an initial headless inspection, start with --bare or disable hooks for that run with --settings '{"disableAllHooks": true}'.
Which tool
Hooks vs CLAUDE.md vs permission rules vs sandbox
Four mechanisms shape agent behavior, and they are not interchangeable:
- CLAUDE.md is guidance: it shapes what the agent tries to do, but it is advice, not enforcement.
- Permission rules are static authorization: allow, ask, and deny, evaluated every time.
- Hooks are runtime programmatic decisions: arbitrary logic at defined lifecycle points.
- OS sandboxing is the actual resource boundary: what the process can touch regardless of the above.
Reach for the weakest tool that solves your problem, and remember that only the last one is a hard boundary. The reasoning is in AI agent sandboxing.
One layer up
Policy and isolation, handled at the infrastructure layer
HarnessRouter is the world's first unified interface for agent harnesses. A unified interface means one API contract for running complete agent harnesses: starting tasks, streaming progress, continuing sessions, and collecting files and results work the same way across every harness on the platform.
Hooks are a local policy layer for one machine; when runs move server-side, each executes inside its own isolated per-run sandbox, so the resource boundary stops depending on a hook you configured correctly. One honest limit: a sandbox still reaches whatever you explicitly grant it, including mounted data, injected credentials, and permitted network targets. Isolation bounds the blast radius; it does not remove the need to grant carefully.
FAQ
Claude Code hooks FAQ
Where are Claude Code hooks configured?
Hooks can be defined in ~/.claude/settings.json for user scope, .claude/settings.json for project scope, .claude/settings.local.json for local scope, managed policy settings, plugin hooks/hooks.json, skill frontmatter, or subagent frontmatter. Project settings are the common home for repository-specific automation, and ${CLAUDE_PROJECT_DIR} lets a project hook reference scripts from the project root.
What is the difference between PreToolUse and PostToolUse?
PreToolUse runs before a tool call and can block or change it. PostToolUse runs after the tool has already executed, so it can react, run tests, or give feedback, but it cannot undo the action.
Which exit code blocks a Claude Code tool call?
For a command PreToolUse hook, exit code 2 blocks the tool call, and text written to standard error becomes Claude denial feedback. Exit code 0 with no output reports no hook decision, so the call continues through the normal permission flow. Other non-zero exit codes generally produce non-blocking errors.
Can a hook override a deny rule?
No. Explicit deny and ask rules are still evaluated, so a hook cannot turn a denied action into an allowed one. A hook can add a block, but it cannot loosen the permission system.
Are Claude Code hooks safe in headless mode?
Only when the hooks come from a source you trust and run with limited privileges. In a -p or SDK session, Claude Code does not show the workspace trust dialog and treats the folder as trusted, so review repository settings and referenced scripts, use --bare, or disable hooks for the initial run.
Run agents with policy and isolation built in
Start a task through one API and let every run execute server-side in its own isolated sandbox, with sessions, streaming, and artifacts handled for you.
Run your first agent free

