Documentation Index
Fetch the complete documentation index at: https://agentic.proxify.io/llms.txt
Use this file to discover all available pages before exploring further.
New to hooks? — Start with the
official hooks guide for the full API, event types, matchers, and configuration syntax. This page is the opinionated playbook for making hooks work
together.
The Enforcement Ladder
Claude Code has three enforcement layers. Most teams only use one or two — using all three is what makes guardrails airtight.
| Layer | Mechanism | Strength | Example |
|---|
| CLAUDE.md | Natural language instructions | Suggestion — Claude usually follows it | ”Don’t commit .env files” |
| Hooks | Code that runs on lifecycle events | Enforcement — blocks or modifies actions | PreToolUse hook exits with code 2 if .env is staged |
| Permissions | allowedTools and permission mode | Access control — tools can’t execute at all | Bash restricted to specific commands only |
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.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/lint-and-format.sh"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": ".claude/hooks/verify-tests-pass.sh"
}
]
}
]
}
}
How it works:
PostToolUse runs your linter and formatter after every edit — Claude sees violations in real-time and self-corrects
Stop runs your test suite before Claude finishes responding — if tests fail, exit code 2 forces Claude to continue and fix them
The 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.
| Phase | Event | What it does |
|---|
| Save before compaction | PreCompact | Writes critical state (current task, decisions made, blockers) to a scratch file |
| Save on session end | Stop | Persists learnings and progress to a file |
| Restore on session start | SessionStart | Loads the saved context back into Claude’s window |
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.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/protect-configs.sh"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": ".claude/hooks/audit-config-changes.sh"
}
]
}
]
}
}
Why this exists: Claude will sometimes modify .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:
| Instead of… | Consider… | Why |
|---|
UserPromptSubmit for learning hooks | Stop | UserPromptSubmit fires on every message and adds latency to each prompt. Stop fires once per response — lightweight. |
PostToolUse for security blocks | PreToolUse | Post is too late — the action already happened. Pre blocks it before execution. |
Stop for observation/telemetry | PostToolUse | Stop is less reliable for continuous observation. PostToolUse fires on every tool call at 100% reliability. |
SessionEnd for saving state | Stop + PreCompact | SessionEnd has a 1.5s timeout by default. Stop and PreCompact have full execution time. |
Hook Types Beyond Command
The official docs cover four hook types. Here’s when to reach for each:
| Type | Best for | Trade-off |
|---|
| Command | Most hooks — lint, format, block, audit | Requires a script file. Fast. |
| HTTP | Centralized logging, remote validation, team dashboards | Adds network latency. Non-2xx fails open. |
| Prompt | Yes/no decisions that need judgment (“is this command safe?”) | Uses an LLM call — slower, costs tokens. |
| Agent | Verification that requires tool use (“does this file exist?”, “does this match the schema?”) | Spawns a subagent — slowest option. |
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
Commit to .claude/settings.json | Keep in .claude/settings.local.json or ~/.claude/settings.json |
|---|
Security blocks (.env, force-push, rm -rf) | Auto-format (team may use different formatters) |
| Linting (team standard) | Editor-specific hooks |
| Test runners | Notification preferences |
| Config protection | Experimental hooks you’re testing |
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.
// Don't do this
{ "command": "if echo \"$CLAUDE_TOOL_INPUT\" | grep -qE 'git add.*\\.env'; then echo 'BLOCKED' >&2; exit 1; fi" }
// Do this
{ "type": "command", "command": ".claude/hooks/block-env-staging.sh" }
LLM config gaming. Claude will modify linter configs to disable rules rather than fix code. If you auto-lint with a 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.