Claude Code Kit

Hook Reference

Every built-in hook — what it does, when it runs, and a link to its source on GitHub.

Hook Reference

All hooks live in .claude/hooks/ and receive the tool payload as JSON on stdin. Exit 0 allows the action; exit 2 blocks it (PreToolUse and Stop). PostToolUse hooks can't block the call that already ran — they warn, inject context, or persist state for a later gate.

The kit ships 27 hooks. The standard profile wires up 23; four are opt-in (marked below). Each links to its current source — that's the source of truth, so this page never drifts.

Shared parsing/state helpers live in .claude/hooks/lib/ (json-parse.sh, state-counter.sh, project-commands.sh). The blocking hooks fail closed: if the library can't be loaded they exit 2 rather than silently passing.

Want to enable the opt-in hooks or write your own? See Enable Opt-In Hooks and the Hooks guide.

SessionStart

session-start.sh

Injects Tier-1 context before the first turn: confirms CODEBASE_MAP.md / CLAUDE.project.md presence, the top rules from tasks/lessons/_index.md, the active task in tasks/todo.md, and the current branch + uncommitted-tree summary. Also resets the transient session state files (hook-firings, quality-gate-history, bash-budget, and last_quality_gate) so a new session starts clean. Emits {"additionalContext": "..."}; stays silent when there's nothing substantive to add.

UserPromptSubmit

prompt-router.sh

Inspects the prompt and injects a domain-specific reminder when it hits a sensitive inflection — auth/security, billing/payments, migrations/schema, deploy/release, dependencies. Uses word-boundary matching so authentication, permissions, migrations, etc. trigger. Silent when nothing matches.

skill-extract-reminder.sh · opt-in

Periodically reminds Claude to capture non-obvious discoveries as a skill. Enabled by the strict profile.

PreToolUse — Edit / Write / NotebookEdit

protect-files.sh

Blocks edits to secret-bearing or generated files — .env* (except .example/.template/.sample/.test), credentials, private keys (*.pem, *.key, id_rsa…), certificates, cloud service-account JSON, and lock files (package-lock.json, pnpm-lock.yaml, composer.lock, Pipfile.lock, …). Exit 2.

protect-changes.sh

Blocks architectural edits unless CLAUDE_APPROVED=1: dependency manifests, migrations/schema, auth-logic paths (*/lib/auth/*, */middleware/auth*, */auth/*, */security/*, */permissions/*), and CI workflows. Presentational UI under */components/* is exempt. Build configs (tsconfig, next.config, tailwind.config, Dockerfile, …) block only when CCK_PROTECT_BUILD_CONFIGS=1 (set by the strict profile); in the standard profile they emit a non-blocking heads-up.

glob-guidance.sh

Non-blocking. Surfaces a one-shot, per-pattern nudge for cross-cutting file patterns that don't map to a single directory where a subdir CLAUDE.md would carry the guidance — test files (*.test.*, *.spec.*, test_*.py) and migrations anywhere in the tree. Emits to stderr (the PreToolUse feedback channel) and always exits 0; fires once per pattern per session via .hook-state/glob-guidance-fired. Customise the case table in the script.

PreToolUse — Bash

branch-protect.sh

Blocks direct pushes to main/master (explicit, HEAD:, or bare push while on the branch) and force pushes. Allows --force-with-lease.

block-dangerous-commands.sh

Blocks hard-to-reverse commands: recursive rm on /, ~/$HOME, ., or a wildcard; git reset --hard; git clean -fd; SQL DROP/TRUNCATE; docker system prune -a; recursive chmod/chown on /.

conventional-commit.sh

Validates git commit -m messages against the conventional-commit format (feat, fix, refactor, test, docs, chore, perf, ci, build, style). Skips heredoc / no--m commits.

PreToolUse — Task

subagent-pre.sh

Appends an open telemetry row to .hook-state/agent-invocations.jsonl for each sub-agent (Task) invocation, so sub-agent usage can be measured.

PreToolUse — MCP

mcp-gate.sh

MCP supply-chain / prompt-injection governance (matcher mcp__.*). Blocks (exit 2) any mcp__<server>__<tool> call whose <server> is absent from .claude/mcp-allowlist.txt — an unexpected or malicious MCP server can't be invoked until you explicitly trust it. Inert until you create that allowlist: with no file it never blocks, and only emits a one-shot per-session reminder that MCP results are untrusted data, not instructions. Audit configured servers against the allowlist with /mcp-audit.

PostToolUse — Edit / Write / NotebookEdit

secret-scan.sh

Scans the edited file for accidentally committed secrets — AWS keys, generic API-key/token assignments, private keys, JWTs, GitHub/Slack/Google/Stripe/SendGrid/Twilio/npm/PyPI tokens, and DB connection strings with credentials. Warns (exit 0); flip to exit 2 to make it blocking.

unicode-scan.sh

Detects invisible Unicode that signals Glassworm-style supply-chain attacks or obfuscation — variation selectors (U+FE00–FE0F, U+E0100–E01EF), zero-width characters, mid-file BOM, and the Tags block. Allowlist a file with a // kit-allow-unicode comment in the first 5 lines. Warns (exit 0).

loop-detect.sh

Tracks recent edits to the same file in a session-scoped temp file. Warns at 4 edits, signals (exit 2) at 6 — a nudge to re-read the goal instead of looping.

quality-gate.sh

After an edit, runs a fast language-appropriate check (TS → tsc --noEmit; JS → npm run lint; Python → ruff/py_compile; Go → go vet; Rust → cargo check) under a 30 s timeout and records the verdict to .hook-state/last_quality_gate.json. Never blocks — it persists evidence for stop-gate.sh to act on. Skipped silently when no suitable tool is found.

auto-lint.sh · opt-in

Runs the project linter (eslint, ruff, clippy, …) after edits.

auto-format.sh · opt-in

Runs the project formatter (prettier, black, gofmt, rustfmt, …) after edits.

skill-compliance.sh · opt-in

Surfaces checklist items from any skill relevant to the edited file.

PostToolUse — Bash

bash-budget.sh

Tracks cumulative Bash output (chars ÷ 4 ≈ tokens) across the session and emits a one-shot warning when it crosses a threshold (BASH_BUDGET_THRESHOLD, default 50 000) — Bash output is the #1 context-window consumer. Measures and signals; never rewrites or truncates.

PostToolUse — Read

read-budget.sh

Symmetric to bash-budget for file reads: tracks cumulative Read output (chars ÷ 4 ≈ tokens) and emits a one-shot warning when it crosses READ_BUDGET_THRESHOLD (default 100 000) — a nudge toward tiered, on-demand loading instead of reading whole trees. Never blocks; writes .hook-state/read-budget.json.

PostToolUse — Task

subagent-post.sh

Closes the latest open row in .hook-state/agent-invocations.jsonl with finished_at + duration_seconds, completing the sub-agent telemetry pair.

PostToolUseFailure

tool-failure-observe.sh

Fires after a tool call errors (a Bash non-zero exit, a failed Edit, …). Pure observability — it can't prevent the failure. Counts failures per session by tool in .hook-state/tool-failures.json; session-end.sh folds the total into the /scorecard so a thrashing session (lots of failed tool calls) is visible. Always exits 0.

Stop

stop-gate.sh

Reads .hook-state/last_quality_gate.json and blocks completion (exit 2) when the last quality gate failed — enforcing CLAUDE.md → Verification deterministically. Escape hatch: SKIP_QUALITY_GATE=1 (or CLAUDE_SKIP_QUALITY_GATE=1). Runs before task-complete-notify.sh.

task-complete-notify.sh

Desktop notification + sound when Claude finishes (after stop-gate.sh clears). macOS (osascript) and Linux (notify-send).

StopFailure

stop-failure-observe.sh

Fires only when a turn ends on an API-level error (rate limit, auth, server) — not on a deliberate stop-gate block. Its stdout/exit are ignored by Claude Code (notification/logging only), so it just records the API-error count + last message in .hook-state/stop-failures.json. The /scorecard reads it to tell "the session died on infra" from "the agent skipped its steps".

SessionEnd

session-end.sh

Appends a JSON-line audit record to reports/session-audit.log (session id, reason, duration, last quality-gate status) plus scorecard inputs, so /scorecard can summarize sessions. The reports/ directory is self-gitignored.

journal-fold.sh

Folds /note journal findings/decisions — and the inter-agent handoff scratchpad — into tasks/handoff-<session-id>.md at session end, so mid-session memory survives into the next session.