Skip to main content

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.
LayerMechanismStrengthExample
CLAUDE.mdNatural language instructionsSuggestion — Claude usually follows it”Don’t commit .env files”
HooksCode that runs on lifecycle eventsEnforcement — blocks or modifies actionsPreToolUse hook exits with code 2 if .env is staged
PermissionsallowedTools and permission modeAccess control — tools can’t execute at allBash 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.
PhaseEventWhat it does
Save before compactionPreCompactWrites critical state (current task, decisions made, blockers) to a scratch file
Save on session endStopPersists learnings and progress to a file
Restore on session startSessionStartLoads 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 hooksStopUserPromptSubmit fires on every message and adds latency to each prompt. Stop fires once per response — lightweight.
PostToolUse for security blocksPreToolUsePost is too late — the action already happened. Pre blocks it before execution.
Stop for observation/telemetryPostToolUseStop is less reliable for continuous observation. PostToolUse fires on every tool call at 100% reliability.
SessionEnd for saving stateStop + PreCompactSessionEnd 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:
TypeBest forTrade-off
CommandMost hooks — lint, format, block, auditRequires a script file. Fast.
HTTPCentralized logging, remote validation, team dashboardsAdds network latency. Non-2xx fails open.
PromptYes/no decisions that need judgment (“is this command safe?”)Uses an LLM call — slower, costs tokens.
AgentVerification 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.jsonKeep 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 runnersNotification preferences
Config protectionExperimental 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.