Claude Code Hooks: Deterministic Control (Not Just Shell Scripts)
Hooks are the part of Claude Code that does not depend on the model remembering anything. They are configured handlers — shell commands, HTTP calls, MCP tool calls, and model-judgment prompts — that the harness runs at named points in a session, whether or not the assistant decides to. This guide covers the events, the five handler types, hooks versus skills, and, as of 2026-10-03, the new kind of hook Anthropic shipped in Claude Code 2.1.287: mods, built from JavaScript function hooks that run inside Claude Code itself.
Correction, October 3, 2026: single-model benchmark latency is a mean, not a median. Labels were corrected; measured values and the original run dates are unchanged. Measurement details.
What hooks actually are
The official definition: hooks are user-defined handlers that execute at specific points in Claude Code's lifecycle, providing deterministic control so certain actions always happen rather than relying on the LLM to choose to run them.
Deterministic is the key word. If you put "always run prettier after editing a file" in your project instructions or a CLAUDE.md, the model may or may not do it, depending on how full the window is. In a PostToolUse hook, the harness runs it after every matching edit; the model cannot forget what it never had to remember.
Configuration lives in a settings JSON file under a single top-level hooks object. Each key is an event name that maps to an array of entries shaped like { matcher, hooks: [{ type, command }] }. Scopes are ~/.claude/settings.json (every project), .claude/settings.json (shared with the team) and .claude/settings.local.json (private); managed policy, plugins and skill frontmatter can add more. Since 2.1.287, Anthropic's mods pages call this kind a settings hook, to tell it apart from a mod's function hooks — more on that below. Our Claude Code walkthrough shows where these files live.
The lifecycle events
Older posts list nine "classic" events. When we read the official Hooks reference on 2026-10-03 we counted 33 events, including newer ones such as Setup, UserPromptExpansion, PermissionRequest, PermissionDenied, PostToolBatch, MessageDisplay, TaskCompleted, FileChanged, PreModelSwitch and PostModelSwitch. Some are version-gated; the count is a dated figure.
For everyday work, anchor on a small, stable core:
Exit code 2 blocks a PreToolUse call, erases a UserPromptSubmit prompt, and prevents Stop from ending the turn, but on PostToolUse it only shows stderr to Claude, because the tool already ran. There is no blanket "exit 2 blocks everything" rule.
Beyond shell scripts
"Hooks are just shell commands" is the most outdated claim in circulating explainers. The reference lists five handler type values:
- command — a shell command. It receives a JSON event on stdin and answers via exit code and stdout. The workhorse.
- http — POST the JSON event to a URL. Useful for audit logging or triggering CI.
- mcp_tool — call a tool on a connected MCP server.
- prompt — a single-turn model evaluation that returns a decision.
- agent — spawn a subagent to verify before deciding.
Per the Hooks reference, read 2026-10-03, default timeouts differ: 600 seconds for command, http and mcp_tool, 30 for prompt, 60 for agent. A prompt hook takes an optional model field that defaults to the model Claude Code uses for background work. The docs do not say it accepts arbitrary non-Anthropic models, so an earlier version of this page that suggested pointing it at DeepSeek V4 Flash overstated things. If you want a non-Claude judge, the documented route is an http handler aimed at an endpoint you control, which can call whatever model you like.
Mods and function hooks (added 2026-10-03)
Claude Code 2.1.287 added a second kind of hook. The changelog line reads "Added Claude Mods: plugins may now modify deeper behavior." Everything below was read on Anthropic's own changelog and Claude Code docs on 2026-10-03, unless the tier says otherwise.
| Claim | Tier | Source |
|---|---|---|
| Mods shipped in Claude Code 2.1.287 | Confirmed | Anthropic changelog; the GitHub release for v2.1.287 is timestamped 2026-10-01 |
| A mod is a plugin whose JavaScript or TypeScript event handlers, called hooks, run inside Claude Code and can observe, rewrite or answer an event | Confirmed | Mods overview, Claude Code docs |
| Mods can change prompts, tool calls, turns and what the interface draws | Confirmed | Mods overview and reference |
On by default from 2.1.287; the early-access CLAUDE_CODE_ENABLE_FUNCTION_HOOKS variable is now ignored | Confirmed | Mods overview |
| You should know: a built-in mod where a side agent flags things you or Claude might miss; off by default | Confirmed | Changelog 2.1.287 and the built-in mods table |
2.1.288 added $.ui.selection() for mods | Confirmed | Changelog; release timestamped 2026-10-02 |
| Release date of October 1 | Attributed | ccleaks.com, read 2026-10-03; matches the GitHub timestamp |
Nothing here rests on unsourced reporting.
What changes. A settings hook runs outside Claude Code; a function hook is a JavaScript function called in Claude Code's own process, middleware-style: it receives the event and a next function, and can pass the event on, pass on a rewritten copy, or answer it itself. The documented events go well past the settings list: tool.call and tool.check around every tool call, prompt.submit and prompt.section for the prompt and individual system-prompt sections, turn.step before each model request (it can swap the model or effort), and ui.render to redraw parts of the interface such as the spinner or a tool call's row. Mods can also draw panes, a band above the prompt and toasts, and see every settings hook event as classic.<Event>.
What does not change. Anthropic's admin page says settings hooks keep working alongside mods and nothing about them is deprecated.
The catch for anyone relying on hooks as guardrails. Mods are not sandboxed and run with your permissions. The docs say a mod can approve a tool call that an ask rule would prompt for, or that one of your own PreToolUse hooks blocked. Order matters too: a PreToolUse hook in managed settings runs before any mod and its block is final, but PreToolUse hooks from other settings files and plugins run after the last mod, so a mod that answers a tool call itself keeps them from running. So the guarantee in the first section is now absolute only for managed hooks; a project hook is deterministic relative to the mods you installed. Where Claude Code's built-in guard loads (machines with managed settings, or a Team or Enterprise sign-in), deny rules still beat a user's mod. Before installing one, run claude plugin validate on it to list the events it handles and the APIs it calls. To switch installed mods off, disable the plugin, start with --safe-mode, or set disableAllHooks, which also stops your settings hooks; none of these stops the mods built into Claude Code.
You should know is the built-in mod the 2.1.287 changelog names. Per the docs, a side agent watches longer tasks and posts a note above the prompt; it is off by default, and the changelog gives /plugin enable cc-plugin-you-should-know@builtin as the switch, and the docs say it appears in /plugin only if it is available for your organization.
Hooks vs skills vs slash
Anthropic merged custom slash commands into skills: .claude/commands/deploy.md and .claude/skills/deploy/SKILL.md both create /deploy. Slash is now one way to invoke a skill, not a separate primitive.
A skill is a SKILL.md file: frontmatter with a description that tells Claude when to use it, plus markdown instructions. Claude loads it when relevant, or you run it by name; disable-model-invocation: true makes it manual-only. Our skills deep-dive covers authoring.
| Dimension | Settings hook | Mod (function hook) | Skill / slash |
|---|---|---|---|
| Who initiates | The harness, at an event | The harness, at an event | Claude when relevant, or you by name |
| What you write | A script plus a settings.json entry | JavaScript or TypeScript in a plugin | Markdown |
| Model token cost to stay active | Zero for command and http | Zero unless it calls a model | ~0 until fired |
| Can block | Yes; final only in managed settings | Yes, and can approve too | No |
| Can draw in the interface | No | Yes | No |
| Best for | Guardrails, formatting, logging | Panes, custom commands, rewriting events | Reusable methods and checklists |
Skills are model- or human-initiated. Hooks of both kinds are harness-initiated: the settings hook is the boring, auditable one; the mod is the powerful one you review before installing.
Config recipes
Three examples from the getting-started guide. First, a desktop notification when Claude needs you:
{
"hooks": {
"Notification": [
{ "matcher": "", "hooks": [
{ "type": "command",
"command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'" }
] }
]
}
}
Second, format-on-edit. Matchers are tool names and can be piped, so Edit|Write catches both. An empty matcher or * matches all; a matcher with any other special character is read as an unanchored JavaScript regular expression, and MCP tools match as mcp__<server>__<tool>.
{
"hooks": {
"PostToolUse": [
{ "matcher": "Edit|Write", "hooks": [
{ "type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }
] }
]
}
}
Third, blocking a dangerous command. A PreToolUse hook matched on Bash inspects .tool_input.command with jq and, when it sees something destructive, exits 0 and prints JSON with hookSpecificOutput.permissionDecision set to deny plus a permissionDecisionReason. Instead of denying, a PreToolUse hook can return updatedInput under hookSpecificOutput to rewrite the tool's arguments before it runs. The if field (permission-rule syntax such as Bash(rm *)) narrows the match without a script. If this guard must survive mods, ship it in managed settings. To see what is configured, /hooks opens a read-only browser that labels where each hook comes from; to change anything, edit the JSON. For routing between models, see our Claude Code router and sequential-thinking guides.
Build the hook-driven gate, keep the bill flat
An http hook can call any model behind an OpenAI-compatible endpoint. DataLLM Lab gives you 300+ models behind one key at datallmlab.com/v1, so a gate can use a small fast model while your session uses Opus.
A cheap judge, measured
A gate that calls a model on every tool use multiplies its price and latency by your tool-call count. Four models on our nine-task benchmark, all 9 out of 9:
At the 2026-10-02 list prices, Opus 5.5 cost $4.03 / $0.03, or about 134 times, what Solar Mini 4 did for the same nine passes. Solar Mini 4 is priced at $0.05 in and $0.2 out per million tokens (2026-10-02).
What this does not tell you. Our tasks ask a model to write small, self-contained Python functions. A gate asks it to classify a shell command. Nine self-contained functions cannot separate a frontier model from a competent small one — all four of these passed everything. The chart gives you a price and latency floor, not proof of judgment. Test a cheap judge on your own blocked-command log first; note a mod hook gets 10 seconds of its own execution time per event, per the mods reference read 2026-10-03.
Method. Nine Python tasks; generated code runs against hidden asserts in an isolated python3 -I subprocess with a 12-second timeout. Temperature 0, max_tokens 4000, one scored attempt per task; an API-layer failure is recorded separately from a wrong answer. Cost is derived from measured token counts times list price, not a billing statement. Runs go through OpenRouter. Full detail on the methodology page; for API-reported router costs on the same tasks, see our Jev Router test.
Which one to reach for
If a behavior must be guaranteed and can be written as a rule, use a settings hook — free, deterministic, and auditable in one JSON file; put it in managed settings if it must outrank mods. If you need to see or reshape what Claude Code does — a pane, a rewritten prompt section — write a mod, and review it like any code running with your permissions. If it is a reusable method that needs the model's judgment about when to apply it, write a skill, and give it a slash name to keep it dormant until you call it. Reserve CLAUDE.md for always-relevant context; you pay for it every turn. On Claude Opus 4.8 or Qwen3 Coder Next, guardrails in hooks keep the window lean. For the security side of letting code approve tool calls, see our agent security piece.
FAQ
What are Claude Code hooks?
User-defined handlers that execute at specific points in Claude Code's lifecycle, giving deterministic control so certain actions always happen instead of relying on the model to choose to run them. They live in a settings JSON file under a single top-level hooks object keyed by event name.
What events can a hook fire on?
The stable core is SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Notification, Stop, SubagentStop, PreCompact and SessionEnd. The official reference now documents around thirty events in total, some version-gated, so treat the full count as current-docs rather than fixed.
Are hooks just shell commands?
No. There are five handler types: command, http, mcp_tool, prompt and agent. The prompt and agent types let a hook make a judgment call with a model, not just match a fixed pattern. The agent type is newer.
How do I block a tool call?
On a PreToolUse hook, exit code 2 blocks the tool and sends stderr back to Claude as the reason. For finer control, exit 0 and print JSON with hookSpecificOutput.permissionDecision set to deny or allow. Exit 2 does not block PostToolUse, since the tool has already run.
Hook or skill — which should I use?
A hook is run by the harness every time at a lifecycle event, costs zero model tokens, and can guarantee or block. A skill is a reusable method Claude loads when relevant, or that you invoke by name. Since slash commands merged into skills, slash invocation is just one way to trigger a skill.
Where do hook configs live?
In settings JSON at three scopes: the user file for all projects, a shareable project file, and a private local project file. Plugins, managed policy, and skill or agent frontmatter can also add hooks. Browse them read-only with /hooks and edit the JSON directly.
What are Claude Code mods, and are they the same as hooks?
Mods shipped in Claude Code 2.1.287. They are built from JavaScript function hooks that run inside Claude Code itself and can change prompts, tool calls, turns and what the interface draws. The hooks this page covers are now called settings hooks, to tell them apart. Mods are not sandboxed and run with your permissions, so a mod can override decisions a settings hook or permission rule would make — see the section above.
DataLLM Lab