A hook is a command you configure that Claude Code itself runs at a fixed point in a session, such as right before a tool call or when Claude finishes answering. The model does not decide whether it runs, so a hook gives you the deterministic control that an instruction in CLAUDE.md cannot.

What it is

Hooks are part of Claude Code’s settings. Each hook ties an event (a moment in Claude Code’s lifecycle) to a handler (what to run). There are more than thirty events. The ones you will use most:

EventFiresCan block
PreToolUseBefore a tool call runsYes, the tool call
PostToolUseAfter a tool call succeedsNo, but it can feed errors back to Claude
PostToolUseFailureAfter a tool call failsNo
PermissionRequestWhen Claude Code is about to ask you for permissionThrough a JSON decision
NotificationWhen Claude is waiting for your input or permissionNo
StopWhen Claude finishes a responseYes, Claude keeps working
SessionStartWhen a session starts, resumes, or restarts after /clear or compactionNo
UserPromptSubmitWhen you send a prompt, before Claude sees itYes, the prompt

A handler has one of five types:

  • command: a shell command. The common case.
  • http: POSTs the event data to a URL.
  • mcp_tool: calls a tool on a connected MCP server.
  • prompt: asks a Claude model a yes/no question about the event, for decisions a fixed rule cannot express.
  • agent: starts a subagent that can read files and run commands before deciding. Experimental.

What problem it solves

Instructions in CLAUDE.md are advice. The model usually follows them, but nothing enforces it, and a long session makes slips more likely. Some things should happen every time without anyone remembering: format the file after each edit, run the type checker, refuse to touch .env, ping you when the agent is stuck on a permission prompt. A hook does that the same way on every turn, whatever the model is thinking.

How to try it

Hooks live under a hooks key in a settings file, in three levels: the event, a matcher group that picks which tool (or notification type) it applies to, and the list of handlers. This one runs Prettier on every file Claude edits:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" }
        ]
      }
    ]
  }
}

Run /hooks in a session to check it appears under PostToolUse. The menu is a read-only browser of every configured hook, grouped by event. Edits to the settings file are normally picked up while the session runs.

Matchers

  • "*", "" or no matcher: matches everything.
  • Letters, digits, _, -, spaces, , and | only: exact names, or a list such as Edit|Write.
  • Any other character: a JavaScript regular expression, not anchored, so Edit.* also matches NotebookEdit.
  • MCP tools are named mcp__<server>__<tool>; match a whole server with mcp__github__.*.
  • An if field narrows a tool hook further with permission-rule syntax, for example "if": "Bash(git *)". It is best-effort, not a security boundary.

Input and output

Claude Code passes the event as JSON on standard input. For a tool event that includes tool_name, tool_input (for Bash, the command; for Edit, the file_path), cwd and session_id.

The hook answers with its exit code:

  • 0: success. Claude Code reads JSON from stdout if there is any.
  • 2: block the action, on events that can block. Stderr goes to Claude as the reason, so it can try another way.
  • Anything else: a hook error. The action goes ahead, and you see the first line of stderr.

Or, on exit 0, the hook prints one JSON object. For PreToolUse the useful fields sit under hookSpecificOutput:

  • permissionDecision: allow, deny, ask or defer.
  • updatedInput: replaces the tool’s arguments before it runs, for example to rewrite a shell command.
  • additionalContext: text placed next to the tool result for Claude to read.

Exit 0 with no output means “no opinion”: the normal permission flow decides.

Where hooks live

LocationApplies toShared
~/.claude/settings.jsonAll your projectsNo
.claude/settings.jsonOne projectYes, committed to the repo
.claude/settings.local.jsonOne projectNo
Managed policy settingsThe organizationSet by an admin
A plugin’s hooks/hooks.jsonWhile the plugin is enabledWith the plugin
Skill or subagent frontmatterWhile the skill or subagent is activeWith the file

Hooks from all these places add up rather than replace each other, and every hook that matches an event runs in parallel. "disableAllHooks": true turns them all off; there is no switch for one hook that keeps it in the file.

Usage example

Frontend

A Vite + React project wants consistent formatting and no edits to the lockfile. In .claude/settings.json, the Prettier hook above runs after every Edit|Write, and a PreToolUse hook on the same matcher blocks protected paths. The script, in Node so the team needs no jq:

// .claude/hooks/protect-files.ts
let raw = "";
for await (const chunk of process.stdin) raw += chunk;
const input = JSON.parse(raw);
const file: string = input.tool_input?.file_path ?? "";
const protectedParts = [".env", "package-lock.json", ".git/"];
 
const hit = protectedParts.find((p) => file.includes(p));
if (hit) {
  console.error(`Blocked: ${file} matches protected pattern ${hit}`);
  process.exit(2); // exit 2 blocks the edit; stderr tells Claude why
}

Register it with "command": "node \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.ts" (Node 24 runs TypeScript directly).

Backend

A Node API repo where agents must never commit to main. A PreToolUse hook on Bash reads tool_input.command, checks the current branch, and exits 2 for git commit or git push on main. Because a crashing hook lets the command through, the hook is started from a small shell launcher that itself exits 2 when the runtime it needs is missing. This is how this wiki’s own repo guards main.

Who it’s for and when

Anyone who keeps telling the agent the same “always” or “never”. Good fits:

  • formatting and type checks after edits, see Automating checks with hooks;
  • blocking paths or commands, as a second line next to permission rules;
  • notifications when a session needs you;
  • trimming noisy command output before it reaches the context (see Reducing token usage).

Skip a hook when the rule needs judgment on every case and a wrong block would get in the way; write it in CLAUDE.md instead, or use a prompt hook.

Gotchas

  • exit 1 does not block. Only exit 2 (or a JSON deny) blocks. A hook that crashes, times out or prints broken JSON lets the action through, unless you set "onFailure": "block" on it (v2.1.295 or later).
  • Not a hard gate. The if filter is best-effort, and a timed-out PreToolUse hook does not block. Use permission rules for a hard allow or deny.
  • But a deny sticks. A hook’s deny blocks the tool even in bypassPermissions mode, and a hook’s allow does not override deny rules from settings.
  • @ files skip PreToolUse. Files you reference with @ are inserted while the prompt is built, without a tool call, so a hook on Read never sees them. Block such paths with a Read deny rule.
  • Two input rewrites race. If two PreToolUse hooks return updatedInput for the same tool, the one that finishes last wins, and the order is not fixed.
  • PostToolUse cannot undo. The tool already ran; exit 2 only shows stderr to Claude.
  • Stop hooks can loop. Claude Code overrides a Stop hook that blocks eight times in a row with no tool call in between. Check stop_hook_active in the input and exit early.
  • Default timeout is 10 minutes for command, http and mcp_tool handlers (30 seconds for prompt, 60 for agent). Set timeout per hook.
  • A cloned repo’s hooks run as you. Hooks in a project’s .claude/settings.json run with your permissions, and in claude -p or the Agent SDK they run even though no trust dialog was ever shown. Read a repo’s hooks before you run an agent in it.
  • Other tools edit your settings too. iTerm2 notes that Claude Code sometimes rewrites ~/.claude/settings.json and can drop the hook its integration added; check /hooks if one goes quiet.

Citations

  1. Automate actions with hooks
  2. Hooks reference
  3. Configure permissions: what runs before you trust a folder
  4. iTerm2: Claude Code Integration