The Enforcement Ladder
Claude Code has three enforcement layers. Most teams only use one or two — using all three is what makes guardrails airtight.
CLAUDE.md is a polite request. Hooks are a locked door. Permissions mean the door doesn’t exist.
The rule: If you’ve written it in CLAUDE.md and it matters, back it up with a hook. If it’s a security boundary, enforce it at the permissions layer too. Each layer catches what the one above misses.
Composition Patterns
Individual hooks are well-documented upstream. The real power is in hooks that work together across lifecycle phases.The Quality Gate
Three hooks that form a closed feedback loop: Claude edits code, sees lint errors immediately, and can’t declare “done” until tests pass.PostToolUseruns your linter and formatter after every edit — Claude sees violations in real-time and self-correctsStopruns your test suite before Claude finishes responding — if tests fail, exit code 2 forces Claude to continue and fix them
Stop hook is the key piece most setups miss. Without it, Claude can write broken code and cheerfully tell you it’s done.
Context Preservation
Three hooks that give Claude memory across context compactions and sessions.
This pattern matters for long tasks where context compaction would otherwise erase important state. The
PreCompact hook fires before the compaction happens — your last chance to save what matters.
The Config Guardian
Two hooks that prevent Claude from gaming your toolchain instead of writing correct code..ruff.toml, biome.json, or tsconfig.json to disable rules rather than fix violations. The PreToolUse hook blocks edits to linter configs before they happen. The Stop hook runs git diff to catch any config changes that slipped through.
Choosing the Right Event
The official docs list 20+ lifecycle events. Here’s when to actually reach for the less obvious ones:Hook Types Beyond Command
The official docs cover four hook types. Here’s when to reach for each:
Command should be your default. Only reach for prompt or agent hooks when the decision genuinely requires reasoning, not just pattern matching.
Team Hook Governance
What to commit vs. keep personal
The principle: If bypassing the hook would break the build or leak secrets, it’s a team hook. If it’s a workflow preference, it’s personal.
Personal overrides without editing team files
When team hooks are too aggressive for exploratory work, override at the user level rather than commenting out committed hooks. User-level settings in~/.claude/settings.json can disable specific hooks without touching the project config.
Anti-Patterns
Inline one-liners. Long shell commands jammed into the"command" field are fragile, hard to test, and impossible to debug. Use dedicated script files in .claude/hooks/ instead.
PostToolUse hook but don’t protect the linter config with a PreToolUse hook, you have a hole.
Trusting community hooks blindly. Hooks execute arbitrary code. A malicious PostToolUse hook can silently exfiltrate environment variables — including API keys — after every Bash execution. When reviewing PRs or installing community configs, audit hook changes with the same scrutiny you’d give a postinstall script.
Duplicating what permissions already handle. If you want to block a tool entirely, use allowedTools in settings — don’t write a PreToolUse hook that always exits with code 2 for that tool. Hooks are for conditional logic. Permissions are for absolute access control.