Hooks
Hooks are the part of the engine that survives forgetting. A rule can be ignored and a skill can go uninvoked, but a hook is executed by the harness — not by Claude — so it fires whether or not anyone remembers it.
19 hooks across 7 events, configured in .claude/settings.json. setup.sh merges that file into ~/.claude/settings.json — the hooks key is engine territory and is replaced wholesale — so a hook wired anywhere else is wiped at the next setup. Wire new hooks here, never by hand in user settings: that is how the report and block hooks below sat unregistered until 2026-09-28.
Active hooks
| Event | Matcher | What it does |
|---|---|---|
SessionStart | * | Inject engine context — sync drift, bridge handoffs (session-engine-context.sh, kun only) |
SessionStart | * | Nag if the machine's maintain heartbeat is stale or RED |
SessionStart | * | Warn if product flow recordings or media library are stale, and when render caches pass 2 GB |
SessionStart | * | Devices brief — disk, swap, open sessions, the kun-rc server (devices.sh --brief) |
SessionStart | * | List the open report-an-issue queue across the databayt repos (session-start-reports.sh) |
UserPromptSubmit | — | Block protocol: inject a named block's records and obligations (block-context.sh) |
ConfigChange | * | Warn when vocabulary.json or the plugins drift from their sources (async) |
PreToolUse | Bash | Block destructive commands — the Unforgivable list (block-destructive-bash.sh) |
PreToolUse | Bash | Scrape guard — account-risk readers and the session vault (scrape-guard.sh) |
PreToolUse | Bash | Funnel guard — outreach sends stay behind their gate (funnel-guard.sh) |
PreToolUse | Bash | Media guard — no video, image/audio over 2 MiB or Thmanyah font gets committed to any repo; media goes to the CDN (media-guard.sh) |
PostToolUse | Write|Edit | Prettier the changed file — only in a repo with a prettier config (format-on-write.sh) |
PostToolUse | Write|Edit | Log the touched file for the block guard (block-touch.sh) |
PostToolUse | Bash | Record scrape yield (scrape-yield.sh, async) |
PostToolUse | Bash | Record funnel yield (funnel-yield.sh, async) |
PostToolUse | mcp__figma__.* | Count Figma MCP calls against the free plan's ~20/month and warn from the 15th (figma-quota.sh) |
Stop | * | Append a timestamp to ~/.claude/session-log.txt |
Stop | * | Block protocol: one nudge when 2+ code files in a block changed without record/docs updates (block-guard.sh) |
Notification | * | macOS notification when Claude needs attention |
Hook scripts live in .claude/hooks/ (each *.test.sh beside its hook) and setup.sh installs them to ~/.claude/hooks/. Each command tries the project copy first and falls back to ~/.claude/hooks/, so the fleet-wide hooks run in every repo while kun sessions run the working copy.
Configuration
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "f=\"${CLAUDE_PROJECT_DIR}/.claude/hooks/block-destructive-bash.sh\"; [ -f \"$f\" ] || f=\"$HOME/.claude/hooks/block-destructive-bash.sh\"; bash \"$f\""
}
]
}
]
}
}Every hook ends in exit 0 (or swallows its own failure). Exit code 2 is the one that blocks: on PreToolUse it stops the tool call (that is how block-destructive-bash.sh works); any other non-zero code is reported as a hook error and the call goes ahead. A Stop hook blocks by printing {"decision": "block", "reason": …} (block-guard.sh).
Events
| Event | Fires | Used for |
|---|---|---|
SessionStart | New session begins | Context injection, drift warnings |
ConfigChange | Config file changes | Regeneration checks |
PreToolUse | Before a tool runs | Validation, guards, cleanup |
PostToolUse | After a tool completes | Formatting, browser open |
Stop | Agent finishes a turn | Logging, protocol nudges |
Notification | Claude pauses for input | Desktop alerts |
Matchers
| Pattern | Matches |
|---|---|
Bash(pnpm dev) | One specific bash command |
Bash | Every bash call |
Write|Edit | Multiple tools, pipe-separated |
* | Everything (events without a tool) |
Block protocol enforcement
The block protocol is hook-enforced rather than trusted to memory: block-context injects a block's records on prompt, and block-touch + block-guard raise a Stop nudge when two or more code files change without the matching record and docs updates.
Adding a hook
Edit .claude/settings.json — event, matcher, command. Keep the command idempotent and non-blocking unless blocking is the point.