Software Engineeringunpacked

Claude Code hooks: the events, a working example, and how to block a tool call

Claude Code hooks are your own commands (or HTTP calls, MCP tools, prompts or subagents) that run automatically at fixed points of a session: before a tool runs, after it returns, when Claude stops, and more. You configure them in a settings file with an event, a matcher and a handler. A PreToolUse hook that exits with code 2 blocks the tool call, which makes hooks the place for rules that must always hold.

What are Claude Code hooks?

The Claude Code docs define hooks as "user-defined shell commands, HTTP endpoints, MCP tool calls, LLM prompts, or subagents that execute automatically at specific points in Claude Code's lifecycle". In practice: you tell Claude Code "every time X happens, run this", and it does, without asking the model.

That last point is the whole value. A line in CLAUDE.md asks Claude to run the linter after editing; a PostToolUse hook runs it, every time.

The Claude Code hook events you will use most

Event When it fires Typical use
SessionStart A session begins or resumes Load context, set up the environment
UserPromptSubmit You submit a prompt, before Claude sees it Add context, reject a prompt
PreToolUse Before a tool call runs (can block it) Guard dangerous commands and protected files
PermissionRequest A tool call needs a permission decision Auto-answer known cases
PostToolUse After a tool call succeeds Format, lint, test, log
PostToolUseFailure After a tool call fails Collect errors
Notification Claude Code sends a notification Desktop or chat alerts
SubagentStart / SubagentStop A subagent starts or finishes Track delegated work
Stop Claude finishes responding Final checks, a summary, a sound
PreCompact / PostCompact Around context compaction Archive the full transcript
SessionEnd The session terminates Clean up

There are more (task, worktree, file change, model switch and elicitation events among them); the reference lists them all.

Where to configure Claude Code hooks

File Applies to
~/.claude/settings.json All your projects
.claude/settings.json One project, shared with the team through git
.claude/settings.local.json One project, only you
Managed policy settings The whole organization
A plugin's hooks/hooks.json While the plugin is enabled
Skill or subagent frontmatter While that skill or subagent is active

Each hook has three levels: the event, a matcher that filters when it fires (for tool events, the tool name, such as Edit|Write), and one or more handlers.

A Claude Code hooks example: lint after every edit

json { "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": ".claude/hooks/lint-changed-file.sh" } ] } ] } }

A command hook receives the event as JSON on stdin (the tool name, its input, the session), so your script can read which file was edited and lint only that one.

Exit codes: how a hook blocks a tool call

Exit code What Claude Code does
0 Success; any JSON the hook printed is applied
2 Blocking error: on events that can block, the action is stopped
Anything else Non-blocking error: the action goes ahead

The trap is exit code 1. It looks like "failed, so stop", but without valid JSON it is a non-blocking error and the tool call still runs. The docs are explicit: if your hook enforces a policy, use exit code 2.

Instead of exit codes, a PreToolUse hook can print a decision:

json { "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Writes to the production config are not allowed" } }

permissionDecision can be allow, deny, ask or defer, and the reason is shown to Claude, so it can change course instead of retrying blindly.

Hook types: command, HTTP, MCP tool, prompt and agent

Hooks vs CLAUDE.md vs permissions

Runs Use it for
CLAUDE.md As instructions Claude reads Conventions and preferences: usually followed
Hook As code at a fixed event Actions that must happen every time, checks that must not be skipped
Permission rules Before every tool call Hard allow and deny lists for tools and commands

The docs add a nuance worth keeping: a hook's optional if filter is best effort, so for a hard allow or deny on a tool, use the permission system; use hooks for the logic around it.

Hooks in the Agent SDK and on the Claude Certified Architect exam

The Claude Agent SDK offers the same events as callbacks in your own code. Task statement 1.5 of the Claude Certified Architect exam guide is about exactly this: PostToolUse hooks that normalize data from different tools (timestamps, status codes) before the model sees it, and interception hooks that block policy-violating calls, such as a refund above a threshold, and redirect to a human. The principle the guide tests: when a business rule needs guaranteed compliance, choose a hook over a prompt instruction, because prompt instructions alone have a non-zero failure rate.

Related: Claude skills can carry their own hooks, and all articles list the rest.

Sources: Claude Code hooks reference; Claude Code overview; Agent SDK hooks; Claude Certified Architect Foundations exam guide v1.0, task statements 1.4 and 1.5.

FAQ

What are Claude Code hooks?

User-defined shell commands, HTTP endpoints, MCP tool calls, LLM prompts or subagents that execute automatically at specific points in Claude Code's lifecycle, such as before a tool call or when Claude finishes responding.

Where do I put Claude Code hooks?

In a settings file: ~/.claude/settings.json for all your projects, .claude/settings.json for a project you share, or .claude/settings.local.json for a project only you use. Plugins, skills and subagents can also define hooks.

How does a hook block a tool call?

A PreToolUse hook blocks the call by exiting with code 2, or by printing JSON with permissionDecision set to deny and a reason. Exit code 1 is a non-blocking error, so the action still goes ahead.

Should a business rule go in a hook or in the prompt?

If it must hold every time, in a hook: hooks run as code, while prompt instructions are followed most of the time but not with certainty.