Hook events¶
Seven events do the work. Everything that blocks uses a JSON decision (or
permissionDecision) with exit code 0, never exit 1, which Claude Code
treats as a non-blocking error.
| Event | Matcher | What runs | Effect on failure | Timeout |
|---|---|---|---|---|
| SessionStart | startup\|resume\|clear\|compact |
Detect toolchains, verify tools, inject the repo's standards and baseline as additionalContext |
Context only; missing tools go to systemMessage |
10s |
| UserPromptSubmit | — | Record prompts that correct Claude as lessons | Context only; never blocks a prompt | 3s |
| PreToolUse | Edit\|Write\|MultiEdit\|NotebookEdit\|Bash |
Policy guards and the dependency check | permissionDecision: deny or ask, with a reason |
10s |
| PostToolUse | Edit\|Write\|MultiEdit |
Auto-format the file, then fast lint on its package; record it as touched | decision: block with the report (the edit already happened) |
5s |
| Stop | — | Full gate over touched packages | decision: block; after 3 consecutive blocks, hands off to you |
120s |
| SubagentStop | test-writer\|implementer |
Same gate, scoped to the files that subagent touched | Same as Stop | 120s |
| SessionEnd | * |
Append a session record to runs.jsonl |
None | 3s |
SessionStart's timeout is generous because the first call may download the binary. PreToolUse allows 10s because the dependency check looks packages up on deps.dev and OSV; it only does so for install commands and manifest edits, and caches what it finds for a day.
The plugin's hooks.json¶
Hooks use exec form (args), so a plugin path containing spaces never
breaks, and blocking hooks fail closed:
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/bin/guardrails",
"args": ["hook", "Stop"],
"timeout": 120,
"onFailure": "block"
}
Session lifecycle¶
- SessionStart with
startuporclearstarts a fresh gate: it recordsHEADas the base commit for patch coverage, snapshots the per-package coverage baseline from the repo cache, and clears touched files and retry counters.resumeandcompactkeep the session's history. - PostToolUse adds the edited file to the touched set (and to the subagent's own set when a subagent made the edit).
- Stop passes immediately when nothing was touched. Otherwise it runs
the gate. A pass resets the retry counter and raises the ratchet baseline
to the new coverage. A failure blocks with
attempt N of 3; the fourth consecutive failure stops blocking, puts the summary insystemMessagefor you, and lets the turn end.
Warn mode¶
With mode: warn, PostToolUse and Stop report failures in systemMessage
instead of blocking. Policy guards are unaffected: they always apply.
Config errors¶
If .guardrails.yaml fails to load (an unknown key, a bad value), quality
checks pause and every event reports the error to you in systemMessage.
Claude is told the config is the human's to fix. Policy guards keep running
with the built-in defaults, so a broken config never disables protection of
the config itself.