> ## 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.

# Hooks Playbook

> Hook composition patterns, governance strategies, and the enforcement ladder — the opinion layer on top of the official docs.

<Tip>**New to hooks?** — Start with the [official hooks guide](https://code.claude.com/docs/en/hooks) for the full API, event types, matchers, and configuration syntax. This page is the opinionated playbook for making hooks work *together*.</Tip>

## 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.

```json theme={null}
{
  "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.

```json theme={null}
{
  "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.

```json theme={null}
// 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.
