✻Founding-member pricing: every lesson for $19/mo. The first 3 lessons are free. Start lesson 1 →
Recipes · 8 min read

8 Claude Code Hooks Worth Copying

Copy-paste Claude Code hook recipes: auto-format, protect files, typecheck gates, notifications, session context, audit logs and more, with how each one works.

By Curtis Cornell · November 14, 2026

Why hooks?

Instructions in CLAUDE.md are requests. Hooks are guarantees: commands Claude Code runs automatically at lifecycle events, every time. Here are eight worth adding today. Each goes under the hooks key in .claude/settings.json (project) or ~/.claude/settings.json (personal).

1. Auto-format every edit (PostToolUse)

json
{ "PostToolUse": [{ "matcher": "Edit|Write", "hooks": [{ "type": "command", "command": "f=$(jq -r '.tool_input.file_path') && npx prettier --write --ignore-unknown \"$f\"" }] }] }

2. Block edits to protected files (PreToolUse)

Point a PreToolUse hook on Edit|Write at a script that checks .tool_input.file_path against a list and exits 2 with a reason on stderr. Pair it with permissions.deny rules for defense in depth.

3. Typecheck gate (Stop)

json
{ "Stop": [{ "hooks": [{ "type": "command", "command": "pnpm tsc --noEmit 1>&2 || exit 2" }] }] }

Claude can't finish its turn until the typecheck passes.

4. Desktop notification (Notification)

json
{ "Notification": [{ "matcher": "", "hooks": [{ "type": "command", "command": "notify-send 'Claude Code' 'Claude Code needs your attention'" }] }] }

On macOS, use osascript -e 'display notification "Claude Code needs your attention" with title "Claude Code"'.

5. Fresh context at startup (SessionStart)

json
{ "SessionStart": [{ "matcher": "startup", "hooks": [{ "type": "command", "command": "echo 'Open PRs:'; gh pr list --limit 5" }] }] }

Stdout from a SessionStart hook is added to Claude's context.

6. Re-inject key context after compaction

Use the compact matcher on SessionStart to print critical reminders after a compaction, so summaries never drop them.

7. Block risky shell commands

A PreToolUse hook on Bash can inspect .tool_input.command and block patterns you never want an agent to run, like force-pushing to main.

8. Lint only what changed

json
{ "PostToolUse": [{ "matcher": "Edit|Write", "hooks": [{ "type": "command", "command": "f=$(jq -r '.tool_input.file_path'); case \"$f\" in *.py) ruff check \"$f\" 1>&2 || exit 2;; esac" }] }] }

The exit-code rule

Exit 0 = no objection. Exit 2 = block, and your stderr becomes Claude's feedback. Learn the full model, including JSON decisions and matchers, in Hooks Fundamentals.

Start lesson 1 free →