Sign inStart free
§Guide

What are Claude Code hooks, and when to use them?

AgentPrep guide cover: What are Claude Code hooks, and when to use them?

A hook in Claude Code is a shell command that runs automatically at a defined point in a session's lifecycle, instead of something a person or the model has to remember to trigger. The command fires every time the event happens, rather than depending on an instruction the model might or might not recall.

Hooks attach to events like a prompt being submitted, a tool call about to run, or a session ending, and some of them can inspect or block what's about to happen. Because a hook is a real command execution, the documentation is explicit about the trade-off: a hook is code that runs on your machine, and it deserves the same scrutiny as any other script you'd let run automatically.

The core idea: automatic, not remembered

Claude Code already lets you steer behavior with instructions, in places like CLAUDE.md or a system prompt — but those depend on the model reading them and choosing to follow them at the right moment. A hook removes that dependency: it's a command the application itself executes at a fixed point, every time, no matter what the model does or doesn't recall.

That's the pitch in one line: a hook turns a suggestion into a guarantee, because it's the shell running a command, not the model choosing to comply, that decides whether it happens.

The events a hook can attach to

Claude Code defines more events than these seven, including PreCompact, PostCompact, SubagentStart, and SubagentStop, among others — but these are the ones most setups reach for first, because they map to the moments people actually want to step in.

How a hook is configured

A hook is JSON written into a settings file, nested three levels deep: a hooks key, then the event name, then an array of entries — each one a matcher paired with its own list of { type: "command", command, timeout }.

Settings can live in more than one place. ~/.claude/settings.json applies to every project on the machine and isn't meant to be shared. .claude/settings.json is scoped to one project and is meant to be committed to git. .claude/settings.local.json is also scoped to one project, but gitignored. Beyond those, an organization can ship managed policy settings, and a plugin can carry its own hooks/hooks.json.

The matcher decides which tool calls an entry applies to. “*”, or leaving it out entirely, matches everything. A matcher made only of letters, digits, _, -, spaces, commas, or | — like “Bash” or “Edit|Write” — is matched as an exact string. Any other character turns the whole matcher into an unanchored JavaScript regular expression, which is what makes something like “^Edit$” behave differently from plain “Edit”.

Blocking, structured output, and who can turn a hook off

A hook controls what happens next mainly through its exit code. Exit code 0 with no output means the hook has no decision to report, so the tool call continues through the normal permission flow. Exit code 2 is the blocking error: on events that can block, exit 2 blocks the action whether or not the hook also printed JSON — even a JSON permissionDecision of “allow” can't override it. Any other exit code is a non-blocking error on most events, and the action goes ahead anyway.

A hook can also return structured JSON instead of relying only on the exit code: a hookSpecificOutput object carries the event name, a permissionDecision of allow, deny, or escalate, and a reason for it, plus universal fields like continue (set to false to stop Claude from processing further), stopReason, and systemMessage.

Hooks can be switched off wholesale with disableAllHooks: true in settings — except for hooks configured through an organization's managed policy settings, which user, project, and local settings can't disable. An administrator can go further with allowManagedHooksOnly, restricting which hooks are allowed to run at all.

A hook defined in a project subagent's frontmatter doesn't run the moment the file appears — it only executes after you accept the workspace trust dialog for the folder that agent file came from, and running a session with -p doesn't count as accepting it. That restriction exists for a plain reason: a hook is code that runs on your machine.

FAQ

What is a hook in Claude Code?

A hook is a shell command that Claude Code runs automatically at a defined point in a session's lifecycle, instead of relying on an instruction the model has to remember. Hooks fire on events like a prompt being submitted or a tool call about to run, and some can block what happens next.

Which events can a Claude Code hook attach to?

The main ones are SessionStart, UserPromptSubmit, PreToolUse, PermissionRequest, PostToolUse, Stop, and SessionEnd, though the full list is longer and includes events like PreCompact and SubagentStart. PreToolUse is the one most commonly used to block a tool call before it executes.

How does a hook block a tool call?

Through its exit code. Exit code 0 with no output means the hook has no decision to report, and the call continues through the normal permission flow. Exit code 2 is the blocking error: on events that can block, it blocks the action even if the hook's own JSON output said to allow it.

Where does the settings file for a Claude Code hook live?

In more than one possible place: ~/.claude/settings.json for every project on the machine, .claude/settings.json for one project (meant to be committed to git), or .claude/settings.local.json for one project (gitignored). An organization's managed policy settings and a plugin's own hooks/hooks.json can also define hooks.

Read next

Hooks run every time, no exceptions. So should the work you put in.

AgentPrep drills the craft this page describes — original scenario questions and mastery tracked per area, in full on Telegram. Free to start in your browser, no card.

Start free
Sources