What hooks are
A hook is a shell command Claude Code runs at a specific moment in the agent lifecycle. Each hook gets a JSON payload on stdin describing what just happened or is about to happen, and the hook's exit code and stdout decide what happens next — allow the action, block it, modify it, log it, notify something.
Hooks are the mechanism that lets you make Claude Code deterministically safe for your environment. A prompt alone cannot guarantee the model will never delete something it should not. A PreToolUse hook on Bash that exits non-zero when the command matches a dangerous pattern can. Same shape for secret scanning, deploy safety, approval gates, audit logging, and team-specific conventions.
They are also how plugins work. The May 2026 Claude Code plugin marketplace is largely a distribution channel for bundles of hooks, slash commands, and sub-agents.
The seven hook types
| Hook | Fires | Can block? | Common use |
|---|---|---|---|
| PreToolUse | Before a tool runs | Yes (exit 2) | Linting, secret scan, deploy safety, path allow-listing |
| PostToolUse | After a tool completes | No (but can feed stderr back to model) | Auto-format, audit log, test runs, notifications |
| UserPromptSubmit | When user submits a prompt | Yes | Secret scan on prompt, prompt enrichment, PII check |
| Stop | Main agent finishes | Can request continue | Final test run, commit prep, session summary |
| SubagentStop | A Task sub-agent finishes | Can request continue | Sub-agent QA, result validation, aggregation |
| Notification | Claude needs user input / idle | No | Desktop notifications, Slack ping, log entry |
| SessionStart | New session begins | No | Load env, warm context, print status |
Four of the seven can influence agent behavior (PreToolUse, UserPromptSubmit, Stop, SubagentStop). Three are observation-only (PostToolUse, Notification, SessionStart). Design your hooks accordingly: use blocking hooks for safety, observation hooks for everything else.
How to configure
Hooks live in a settings file in your home directory (global) or in a project folder (per-project). Project settings merge on top of global.
settings.json with PreToolUse and PostToolUse hooks
A minimal working settings file that wires one PreToolUse hook (bash safety) and one PostToolUse hook (auto-format on edits). The template you copy into your own settings.
View Config Example in companion file JSONThree things worth knowing up front:
- Matcher is a regex matched against the tool name. "Bash" matches only Bash; "Edit|Write" matches both.
- Exit code 2 on a PreToolUse hook blocks the tool call and feeds stderr back to the model. Any other non-zero prints stderr for the user but does not block.
- Stdin carries a JSON payload with tool name, tool input, and session info. Hook scripts parse it with any JSON tool.
Hooks run with your full shell privileges. They can execute arbitrary commands with access to your environment, credentials, and filesystem. Treat the settings file as privileged configuration — review hook scripts before adding them, especially ones from community plugins.
PreToolUseexamples
Runs before a tool executes. The place for safety nets, allow-lists, and conventions enforcement.
Block destructive bash commands
Why: prevent dangerous patterns (recursive deletes on your home directory, force-pushes to protected branches, hard resets without explicit confirmation) from reaching the shell.
View Example 01 in companion file bashPath allow-listing for Write / Edit
Why: restrict file writes to approved directories. Keeps Claude out of dependency folders, env files, and secrets.
View Example 02 in companion file bashSecret scan before any file write
Why: if the model is about to write a file containing an API key, AWS credential, or token pattern, block the write before it hits disk.
View Example 03 in companion file bashProduction-hours deploy guard
Why: block deploy commands (kubectl apply, terraform apply, custom deploy scripts) during peak hours.
View Example 04 in companion file bashPostToolUseexamples
Runs after a tool completes. Does not block, but stderr feeds back into the model so it knows what happened.
Auto-format on file edits
Why: every edit to a source file gets formatted immediately. Code stays consistent without the model thinking about it.
View Example 05 in companion file bashAudit log every tool call to a central file
Why: compliance, debugging, sharing session traces. Append structured records per call.
View Example 06 in companion file bashRun tests after every code change
Why: tight feedback loop. If a test breaks, the next turn sees the failure and can fix forward.
View Example 07 in companion file bashUserPromptSubmitexamples
Runs when the user submits a prompt. Last chance to intercept input before the model sees it.
PII / secret scan on user prompts
Why: prevent engineers from accidentally pasting production credentials into prompts (yes, it happens).
View Example 08 in companion file bashEnrich prompts with current git context
Why: every prompt gets the current branch, dirty-file count, and last commit silently appended so the model always has context.
View Example 09 in companion file bashStopexamples
Runs when the main agent finishes its turn. Last-mile checks and summary.
Run full test suite before finishing
Why: do not let the agent declare victory on a task where something it changed broke tests elsewhere. If a test fails, request continue.
View Example 10 in companion file bashAppend session summary to CHANGELOG
Why: every finished session logs what changed. Great for teams who want an audit trail of agent-driven changes.
View Example 11 in companion file bashSubagentStopexamples
Runs when a Task sub-agent finishes. The place to validate sub-agent output before the orchestrator reads it.
Verify sub-agent produced required artifacts
Why: if you dispatched a sub-agent to "build the API spec" and it stopped without producing the expected file, catch that before the main agent assumes it is done.
View Example 12 in companion file bashNotificationexamples
Runs when Claude needs user input or has been idle. Observation-only; good for pings and desktop notifications.
Desktop ping when Claude is waiting for input
Why: stop context-switching back to the terminal every 30 seconds. Let the OS tell you when attention is needed.
View Example 13 in companion file bashSlack DM when agent is idle more than 5 minutes
Why: for long-running autonomous sessions, know on your phone when attention is needed.
View Example 14 in companion file bashSessionStartexamples
Runs when a new session begins. Set up environment, print status, warm context.
Print project status on session start
Why: every new session opens with a quick status (branch, dirty files, recent commits) so both you and the model have shared context.
View Example 15 in companion file bashCommon patterns worth copying
Four combinations that show up in most serious Claude Code configs:
The safety stack: PreToolUse on Bash (destructive-command block) + PreToolUse on Write/Edit (path allow-list + secret scan) + UserPromptSubmit (prompt-side secret scan). This is the baseline every team should install — it is the difference between "AI assistant that could do damage" and "governed tool with safety nets."
The quality stack: PostToolUse on Edit/Write (auto-format) + Stop (run tests before finish). Keeps shipped code clean without the model needing to remember.
The audit stack: PostToolUse (log every call) + Stop (append changelog) + SessionStart (status snapshot). Full traceability of agent-driven changes.
The notification stack: Notification (Slack or desktop ping). Essential for long-running autonomous sessions.
Install the safety stack first on day one. The others pay off most on week two, after you are comfortable with the tool.
Pitfalls
Hooks run synchronously. A hook that takes 10 seconds to run makes every tool call 10 seconds slower. Keep hooks fast (ideally under 500ms for PreToolUse and PostToolUse). Heavy work belongs in SubagentStop or Stop where latency does not compound.
Hook stderr feeds back to the model. This is a feature when you want the model to see a failure, a bug when a chatty hook floods context. Keep stderr minimal; log verbose output to a file.
JSON parsing errors fail silently. If your hook script breaks because its JSON parser is missing or a key changed in the payload, the hook exits non-zero and the user sees stderr but no explicit error. Add a sanity check at the top of every hook.
Settings are merged, not replaced. Global hooks run plus project hooks. A project adding a PreToolUse hook does not remove the global one. This is almost always what you want — but worth remembering when debugging.
Exit code 2 behavior differs by hook type. On PreToolUse and UserPromptSubmit it blocks and feeds stderr to the model. On Stop and SubagentStop it requests the agent continue. On PostToolUse, Notification, and SessionStart it does neither — just logs stderr for the user. Read the spec carefully for each hook you write.
FAQ
Do hooks work with plugins?
Yes — plugins can ship hooks, and the May 2026 plugin marketplace is largely built around this. A plugin's hooks merge with your personal hooks. Review a plugin's hooks before installing, since they run with your shell privileges.
Can hooks be written in languages other than bash?
Yes. A hook is any command. Python, Node, Deno, Go binaries — whatever you are comfortable with. Bash is the most common because startup cost is lowest. For complex hooks, a compiled binary (Go, Rust) keeps latency low even with substantial logic.
How do I debug a hook that is not firing?
Three checks: (1) matcher regex matches the tool name exactly — "Bash" not "bash"; (2) script has executable permissions; (3) command path in the settings file resolves (use absolute paths, not shell-relative). Tail the Claude logs directory for hook execution records.
Can hooks access MCP tool calls?
Yes. MCP tool names follow a predictable pattern (server name + tool name). Match them with a regex in a PreToolUse matcher to govern all MCP calls, or narrow to a specific server.
Is there a way to share hooks across a team?
Yes — commit the project-level settings file and hook scripts to your repo. Teammates picking up the repo get your hooks automatically. For organization-wide hooks (every repo, every user), distribute as a Claude Code plugin through your internal marketplace.
What is the difference between a hook and a sub-agent?
A hook is deterministic shell code that runs at a fixed lifecycle point. A sub-agent is an LLM invocation dispatched by the main agent. Use hooks for things that must happen deterministically (safety, formatting, tests). Use sub-agents for things that need reasoning (code review of a diff, research on a library, drafting a PR description).