Claude Code Hooks: Map Your Risk Profile to the Hooks You Actually Need
Design Claude Code hooks with a policy prompt that maps your project's risk profile to the lifecycle events to guard and what each hook should enforce.
What Claude Code Hooks Are For
Claude Code hooks are shell commands that run automatically on lifecycle events (PreToolUse, PostToolUse, Stop, and others), giving you deterministic control over an agent's behavior. They fire as commands, not as model prompts, which is the whole point: the model can't forget a hook, can't decide to skip it because the conversation got long. They live in .claude/settings.json for the team or .claude/settings.local.json for you.
Most guides explain the events and hand you example shell commands: the paul-schick PreToolUse/PostToolUse tutorial, the claudefa.st 12-events guide, the ClaudeLog hooks mechanics page. paul-schick's advice is typical and sensible: "start with three hooks." What none of them give you is a way to derive which hooks your specific project needs from what your project can actually damage.
Why "Start With Three Hooks" Isn't Enough
The three-hook starter (block rm -rf, protect .env, auto-format on edit) is a fine default. It's also generic. Your project isn't generic. A repo with production migrations needs a hook the starter set never mentions. A repo that calls a paid API needs a spend guard. A repo with a generated/ tree needs that path protected, not .env.
The right question isn't "what are the common hooks." It's "what can Claude damage here?" That's a risk question, and risk is local. The official Claude Code memory docs make the boundary explicit: CLAUDE.md is context the model tries to follow, while a hook is enforcement that "applies regardless of what Claude decides." If a rule must hold every time, it's a hook, and which rules those are depends on your repo, not a starter list.
A CLAUDE.md line like "never edit generated files" is a request the model usually honors and occasionally doesn't, especially deep in a long session. A PreToolUse hook that returns a non-zero exit code on any Edit under generated/ blocks it every time, deterministically. Use instructions for guidance, hooks for the rules you can't afford the model to miss.
The Policy Contract a Prompt Fills
Instead of copying someone's three hooks, a policy prompt maps your risk profile to a hook plan against a fixed contract:
- Risk inventory. What can go wrong here: data loss, secret exposure, runaway cost, broken production, unreviewed dependency changes.
- Event mapping. Each risk assigned to the lifecycle event that catches it: destructive commands and protected paths to
PreToolUse, formatting and test runs toPostToolUse, scope checks toUserPromptSubmit. - Enforcement per hook. What each hook does: block (non-zero exit), transform, or log. A block is a wall; a log is a paper trail. Don't confuse them.
- Shared vs local split. Which hooks belong in the committed
settings.json(team policy) and which in the gitignoredsettings.local.json(your machine).
That mapping, risk to event to enforcement, is exactly what the tutorials skip and the prompt produces.
Shared Hooks vs Personal Hooks
The split between .claude/settings.json and .claude/settings.local.json isn't cosmetic. The committed file is team policy: every developer on the repo gets the same blocks and formatters, which is what you want for "never force-push to main" or "format Python with ruff on save." The gitignored local file is yours: a noisy logger you're using to debug a workflow, a personal block on a directory only you keep wandering into.
Getting this wrong creates friction. Put a personal experiment in the shared file and you've inflicted it on the whole team. Put a real safety rule only in your local file and a teammate's agent runs unguarded. The rule of thumb: if breaking the rule would hurt anyone on the team, it's shared; if it's about your own habits, it's local.
A generated hook policy can label each hook with its intended file, so the split is a decision you make once rather than a thing you sort out after a teammate complains. That labeling is cheap to produce and annoying to retrofit.
How to Generate Your Hook Policy, Step by Step
1. Inventory the risks
Walk the repo and name what Claude could break: which paths are sacred, which commands are destructive, which operations cost money or hit production.
2. Fill the variables
The prompt takes {{risk_profile}} (the inventory) and {{stack}} (so the example commands fit your tooling, prettier vs ruff, pnpm vs cargo).
3. Run it against the contract
You get a hook plan: each risk mapped to an event, each event with a concrete enforcement and the settings file it belongs in.
4. Wire and test
Add the hooks to .claude/settings.json, then deliberately trigger one. Try a blocked command, and confirm the non-zero exit actually stops it.
One Opinion: Most Teams Over-Block and Under-Log
Here's a stance worth defending. Teams new to hooks reach for blocks everywhere, block this, block that, and end up fighting their own agent, approving past walls all day. Blocking is right for the genuinely destructive: rm -rf, force-push to main, editing secrets. For everything else, a PostToolUse log is usually the better tool.
A log tells you the agent touched the migrations folder without stopping it mid-task, so you review the diff instead of babysitting every step. Reserve blocks for what you can't undo. Log what you want to know about. The split between "stop this" and "surface this for review" is the actual design work, and it's the same Always / Ask-first / Never thinking behind a good guardrails prompt for AI coding agents.
Variables You'll Set
| Variable | Required | What it is |
|---|---|---|
{{risk_profile}} | Yes | What Claude can damage: protected paths, destructive commands, cost or prod risks |
{{stack}} | Yes | Tooling, so the hook commands fit (ruff vs prettier, cargo vs pnpm) |
{{enforcement}} | No | Per-risk preference for block vs log vs transform |
Getting Started
- Inventory what Claude can break in this repo.
- Decide, per risk, whether you need a wall or a paper trail.
- Feed
{{risk_profile}}and{{stack}}to a policy prompt with a fixed contract. - Wire the resulting hooks into
.claude/settings.jsonandsettings.local.json. - Trigger a blocking hook on purpose to confirm it stops the action.
The boundary logic behind hooks is the same as a system-prompt guardrail, so the Coding Agent Guardrails System Prompt produces the Always / Ask-first / Never tiers your hook policy should mirror. If you're enforcing the same rules in CI rather than at the agent's tool calls, the AI PR CI-Gate Harness Agent Pack moves the gate to the pull request.
Browse the prompt packs →Why deterministic enforcement is worth the setup
The model's reliability degrades as context fills. An instruction it followed at turn three can slip at turn thirty. Hooks don't degrade. A PostToolUse formatter runs on the thousandth edit exactly as it ran on the first. For anything you'd be annoyed to find skipped, that consistency is worth the few lines of settings.json. It's the one layer in an agent setup the model genuinely can't talk its way around.
The Coding Agent Guardrails System Prompt turns a {{risk_profile}} into an explicit Always / Ask-first / Never policy, the exact mapping your hook plan needs, in a form Claude also reads as a ## Boundaries block at the instruction layer. It's part of The Complete AI Prompts Bundle, a one-time lifetime license to the whole catalog plus every pack added later, worth it if you guard more than one repo or agent.
Hooks are the deterministic floor under an agent's behavior. Map your real risks to events, block what you can't undo, log the rest. For the instruction-layer companion and the CI-layer one, see Claude Code subagents system prompts and an AI CI gate prompt to block risky PRs.
Common questions
What are Claude Code hooks?
What's the difference between PreToolUse and PostToolUse hooks?
Why use hooks instead of CLAUDE.md instructions?
Get the prompt packs this guide is built on
Ready-to-paste prompts with documented variables and worked examples for ChatGPT, Claude, and Gemini. One-time payment, own it forever.
More prompt guides

Aider Conventions File: Generate a Model-Agnostic CONVENTIONS.md From Your Stack
What an Aider Conventions File Does An Aider conventions file is a markdown file — usually — that tells Aider the coding rules to follow in a repo: library preferences, naming, architecture decisions,…

Cline Rules: Generate a .clinerules Set (and Know When to Use Workflows)
What Cline Rules Are Cline rules are plain-text instructions injected into the system prompt of every conversation Cline has in a project. They live in a file or a directory at the project root, and C…

Windsurf Rules: Generate a Lean .windsurfrules File for Cascade
What Windsurf Rules Are and Why the File Stays Small Windsurf rules are explicit, project-level instructions you write into so Cascade — Windsurf's agent — follows your conventions on every interactio…