Claude Code hooks: a practical guide with examples Anthropic's Claude Code hooks, documented in the hooks guide and reference as of Claude Code 2.1.280 on September 23, 2026, run commands deterministically at more than thirty session events, and a PreToolUse hook can block a tool call outright by exiting with code 2. Hooks are configured under a `hooks` key in settings files such as `.claude/settings.json`, where matchers like `Edit|Write` pair with commands, and their location determines scope from a single project to an entire organization via managed policy settings. Unlike advisory `CLAUDE.md` instructions, hooks always run, which is why teams convert ignored rules into hooks. Blog Claude Code hooks: a practical guide with examples Published: September 23, 2026 Claude Code hooks are commands that run at fixed points in a session: before a tool runs, after a file is edited, when a prompt is submitted, when Claude stops. Unlike a line in CLAUDE.md , a hook is not advice. It always runs, and a PreToolUse hook can block the action outright by exiting with code 2. Checked against the hooks guide https://code.claude.com/docs/en/hooks-guide and reference https://code.claude.com/docs/en/hooks on 2026-09-23 Claude Code 2.1.280 . The event list has grown to more than thirty; the handful below are the ones most teams use. What are Claude Code hooks? Anthropic's best practices https://code.claude.com/docs/en/best-practices put it in one line: unlike CLAUDE.md instructions, which are advisory, hooks are deterministic and guarantee the action happens. A rule asks the model. A hook does not ask anyone. That is why the answer to “Claude keeps ignoring my rule” is so often “make it a hook”, and why the causes behind the ignoring, covered in why Claude ignores CLAUDE.md https://gethrbr.com/blog/why-claude-ignores-claude-md , do not apply to one. Which hook events should you know? | Event | Fires | Typical use | |---|---|---| | PreToolUse | Before a tool call runs. Can block it | Refuse dangerous commands, protect files | | PostToolUse | After a tool call succeeds | Format or lint the file that was just edited | | UserPromptSubmit | When you submit a prompt, before Claude sees it | Add context to the prompt, such as branch state | | SessionStart | When a session begins, resumes, clears or compacts | Load context the session should start with | | Stop | When Claude finishes responding | Run the tests and refuse to stop until they pass | | PreCompact / PostCompact | Around context compaction | Re-inject what must survive a summary | | SessionEnd | When the session terminates | Write logs, clean up | | Notification | When Claude Code sends a notification | Desktop alert when Claude is waiting for you | How do you configure a hook? Hooks live in a settings file under a hooks key. Each event takes matchers a tool name or a regex such as Edit|Write and the commands to run. This formats every file Claude edits: // .claude/settings.json { "hooks": { "PostToolUse": { "matcher": "Edit|Write", "hooks": { "type": "command", "command": "jq -r '.tool input.file path' | xargs npx prettier --write" } } } } Where the block goes decides who it applies to: | Location | Scope | Shared | |---|---|---| | ~/.claude/settings.json | All your projects | No | | .claude/settings.json | This project | Yes, commit it | | .claude/settings.local.json | This project | No, gitignored | | Managed policy settings | The whole organization | Admin controlled | | Plugin, skill or subagent | While that is enabled or running | Yes, ships with it | Run /hooks inside Claude Code to see everything configured, grouped by event. How do you block a command with a PreToolUse hook? The hook receives the tool call as JSON on stdin. Exit 0 lets it through. Exit 2 blocks it, and what you write to stderr goes back to Claude as the reason, so it can change course. This is the pattern from Anthropic's guide, protecting files: bash /bin/bash .claude/hooks/protect-files.sh INPUT=$ cat FILE PATH=$ echo "$INPUT" | jq -r '.tool input.file path // empty' for pattern in ".env" "package-lock.json" ".git/"; do if "$FILE PATH" == "$pattern" ; then echo "Blocked: $FILE PATH matches protected pattern '$pattern'" &2 exit 2 fi done exit 0 Register it on PreToolUse with the matcher Edit|Write , make it executable, and ask Claude to edit .env to test it. For structured control, exit 0 and print JSON with permissionDecision set to deny , ask or allow . When several hooks answer, the most restrictive wins. Which commands are worth blocking? The ones your agents actually run, not the ones that sound scariest. A guard written from imagination can sit for months without matching anything. The costly commands are often ordinary: rm -r on a source directory, git checkout --