The Problem With Polite Prompts
"Please always run Prettier after editing files."
I've written this in CLAUDE.md files, system prompts, and project instructions. And Claude follows it — most of the time. Sometimes it forgets. Sometimes it decides the file doesn't need formatting. Sometimes it's in the middle of a complex task and skips it.
That's the fundamental problem with prompt-based instructions: they're suggestions, not guarantees. Hooks fix this. They turn "please do X" into "X happens every single time, no matter what."
I've been running Claude Code with hooks in production workflows for months. Here's every pattern that actually matters.
What Hooks Actually Are
Hooks are user-defined commands that fire automatically at specific points in Claude Code's lifecycle. They intercept the AI — the AI doesn't control them.
Three types exist:
- Command hooks — shell scripts that receive JSON on stdin, communicate via exit codes
- Prompt hooks — send a prompt to Claude for a single-turn yes/no decision
- Agent hooks — spawn a subagent with Read/Grep/Glob access to verify conditions
The critical distinction: hooks run with your system user's full permissions. No sandbox. They can read, modify, or delete anything your account can access. Anthropic's own disclaimer: "USE AT YOUR OWN RISK."
Configuration lives in three places:
~/.claude/settings.json— user-wide, applies everywhere.claude/settings.json— project-specific, commit to version control.claude/settings.local.json— personal overrides, don't commit
Since Claude Code 2.1, you can also embed hooks directly in agent and skill frontmatter. More on that later.
The 13 Lifecycle Events
Claude Code exposes 13 hook points. Here's the map:
Session lifecycle:
- Setup — before session starts (via
--init/--maintenanceflags) - SessionStart — when session opens or resumes
- SessionEnd — when session terminates
Every conversation turn:
- UserPromptSubmit — user hits enter, before Claude processes
- PreToolUse — before any tool executes (can block or modify)
- PermissionRequest — when Claude asks for permission (can auto-approve)
- PostToolUse — after tool succeeds
- PostToolUseFailure — after tool fails
- Stop — when Claude finishes responding
Agent coordination:
- SubagentStart — subagent spawns
- SubagentStop — subagent finishes
Notifications and maintenance:
- Notification — permission prompts, idle alerts
- PreCompact — before context window compaction
The exit code system is simple: exit 0 means continue, exit 2 means block the action, anything else is an error.
Pattern 1: Auto-Format on Every Write
The most common hook. After Claude writes or edits any file, Prettier formats it automatically.
Hook on PostToolUse, match Write|Edit|MultiEdit. The command runs npx prettier --write on the file path from $CLAUDE_TOOL_INPUT_FILE_PATH. Chain ESLint after it for lint fixes.
This is deterministic. Claude doesn't decide whether to format. It happens. Every time. I wrote about this kind of deterministic control as the foundation of agentic architectures — hooks are the simplest implementation of that principle.
Pattern 2: Block Dangerous Commands
PreToolUse hook on Bash. The shell script reads the JSON input from stdin, extracts the command, and checks it against a blocklist: rm -rf /, DROP TABLE, git push --force, chmod 777.
Exit 2 to block. Exit 0 to allow. The hook fires before the command runs — Claude never executes the dangerous operation.
This matters more than people think. I've seen Claude attempt rm -rf on directories it shouldn't touch. Not maliciously — just confidently wrong about what needs cleaning up. A 10-line bash script prevents all of it.
Pattern 3: Auto-Approve Safe Operations
PermissionRequest hook with matcher Bash(npm test*)|Bash(npm run lint*). The script returns an allow decision for matching commands.
This eliminates the permission dialog for operations you've already decided are safe. No more clicking "approve" 50 times during a test-driven development session.
Important caveat: PermissionRequest hooks don't fire in non-interactive mode (-p). If you're running Claude Code headlessly, use PreToolUse instead.
Pattern 4: Async Test Runner (Jan 2026)
This is new. Since Claude Code 2.1, hooks can run in the background without blocking Claude's execution. Add "async": true to the hook config.
Boris Cherny announced this on January 25: "Hooks can now run in the background without blocking Claude Code's execution."
PostToolUse hook on Write, async mode. The script triggers your test suite in the background. Claude continues working immediately. When tests finish, results appear on the next conversation turn.
The tradeoff is explicit: async hooks cannot block or control Claude's behavior. The decision and permissionDecision fields have no effect. Each execution creates a separate background process — no deduplication.
Best for: test suites, CI triggers, log shipping, backup creation. Not suitable for anything where Claude needs the result before continuing.
Pattern 5: Setup Hooks — One-Command Onboarding
Also released January 25, 2026. Setup hooks run before the session starts.
Three CLI flags trigger them:
claude --init— runs the setup, then starts the sessionclaude --init-only— runs the setup, then exits (CI-friendly)claude --maintenance— runs maintenance-specific hooks
The killer use case: team onboarding. New developer clones the repo, runs claude --init, and the hook installs dependencies, sets up the database, configures environment variables, and validates everything works. One command.
Weekly insights on AI Architecture. No spam.
The --init-only flag is designed for CI/CD pipelines. It runs the hook and returns an exit code — zero for success, non-zero for failure. No interactive session needed. This connects to the same philosophy I described in building environments instead of personas — the environment configures itself.
Pattern 6: PreCompact Backup
When Claude Code's context window fills up, it compacts the conversation. This is automatic and irreversible — you lose the full transcript.
A PreCompact hook saves the entire conversation to a timestamped file before compaction happens. Simple: the script reads the session data and writes it to .claude/backups/.
I've lost debugging context to compaction more than once. This hook prevents that permanently.
Pattern 7: SessionStart Context Injection
SessionStart hook that loads git status, recent commits, open issues, and your project's TODO list — then feeds it to Claude as initial context.
Every session starts with Claude already knowing what changed since last time. No more "let me check the git log" at the beginning of every conversation.
Pattern 8: Stop Hook — Completion Enforcement
Stop hooks fire when Claude finishes responding. If you return exit 2, Claude is forced to continue.
The pattern: run your test suite in the Stop hook. If tests fail, block completion and feed the failure output back to Claude. Claude sees the failing tests and fixes them before it can finish.
This turns Claude Code into a self-correcting system. The agent cannot declare "done" until the objective criteria pass.
Two gotchas: Stop hooks fire on every response, not just task completion. And they don't fire on user interrupts (Ctrl+C). Design accordingly.
Pattern 9: Tool Input Modification
Since v2.0.10, PreToolUse hooks can silently modify tool inputs before execution. Claude doesn't know the inputs changed.
Use cases that matter:
- Force
--dry-runflags on destructive commands - Redirect file paths to a sandbox directory
- Redact secrets from command arguments
- Enforce commit message formatting
The hook returns updatedInput in its JSON output. Claude sees its original command — the modified version is what actually runs.
This is the most powerful and most dangerous pattern. Invisible modification means invisible bugs if your hook has errors.
Pattern 10: Skill-Scoped Hooks (Claude Code 2.1)
The newest addition. Hooks defined in agent or skill frontmatter files:
A markdown skill file with YAML frontmatter containing hook definitions. The hooks travel with the skill — install the skill, get the governance for free.
This is significant for teams. Instead of every developer configuring hooks manually, the skill author embeds the safety checks. A "database migration" skill includes hooks that block destructive SQL. A "deployment" skill includes hooks that enforce staging-first.
The Three Types: When to Use What
Command hooks for everything deterministic. File formatting, security blocking, test running, backups. Fast, predictable, full control.
Prompt hooks for judgment calls. "Does this file look like it contains secrets?" "Is this commit message descriptive enough?" The LLM evaluates and returns yes/no. Cannot run async.
Agent hooks for complex verification. The hook spawns a subagent with access to Read, Grep, and Glob tools. It can inspect files, check patterns, and make informed decisions. Heavier than prompt hooks, but more capable.
My ratio: 90% command hooks, 8% prompt hooks, 2% agent hooks. Determinism beats intelligence for most operational patterns.
What Hooks Can't Do
Honest limitations:
- PostToolUse hooks can't undo actions. The tool already ran. You can react, but you can't reverse.
- Async hooks can't block. By design. The action already proceeded.
- Stop hooks fire on every response. Not just task completion. Your test suite will run on casual replies too unless you add logic to detect task context.
- No deduplication. If Claude writes 10 files rapidly, your PostToolUse hook fires 10 times. Plan for concurrency.
- SubagentStop prompt hooks are bugged. They send feedback but don't prevent termination (GitHub issue #20221).
Security: The Honest Version
Hooks run arbitrary shell commands with your full user permissions. There is no sandbox.
One safety mechanism exists: if someone modifies your hooks settings file directly (not through Claude Code's /hooks menu), the changes don't take effect immediately. You have to review them. This prevents a compromised tool from injecting malicious hooks mid-session.
Best practices: validate and sanitize all inputs from stdin, quote every shell variable, use absolute paths, block path traversal attempts, and skip operations on sensitive files (.env, credentials).
The Verdict
Hooks are the most underrated feature in Claude Code. They solve the fundamental reliability problem of AI-assisted development: the gap between "Claude usually does X" and "X always happens."
The new additions — async hooks for non-blocking operations, setup hooks for team onboarding, and skill-scoped hooks for portable governance — turn this from a convenience feature into a production infrastructure tool.
Start with Pattern 1 (auto-format) and Pattern 2 (block dangerous commands). Add the rest as your workflow demands. The configuration is JSON in a settings file. No dependencies. No build step. Just deterministic control over probabilistic AI.