Test it, then ship it
Every preset you clicked in this tutorial is a test case, and hooks carry their tests inline. The widget below shows page one’s guard again — this time with its tests block.
uvx capt-hook test runs every hook’s cases through the real dispatch — the same engine the widgets here were verified against:
from captain_hook import Allow, Block, Input, block_command
block_command(
["git", "stash"],
reason="Stashing loses uncommitted agent work",
hint="Commit to a WIP branch instead",
tests={
Input(command="git stash"): Block(),
Input(command="cd /tmp && git stash"): Block(),
Input(command='echo "git stash"'): Allow(),
Input(command="git status"): Allow(),
Input(command='git commit -m "wip"'): Allow(),
},
)
A hook without tests is a regex you’re hoping about. A hook with tests fails your CI when someone loosens the pattern — run uvx capt-hook test --json in a workflow and a regressed guard breaks the build, not the incident review.
One last thing: hooks can ask a model. When no pattern can decide — “is this SQL migration destructive?” — llm_gate hands the verdict to an LLM with your rubric. Below is a recorded verdict, and you can run the same gate live on-device: Chrome’s built-in model, WebGPU, or CPU WASM, whichever this browser supports. The model downloads once, and nothing you type leaves the page:
from captain_hook import Event, Runs, Tool, llm_gate
llm_gate(
"You review database migrations for irreversible data loss. A migration is destructive when it "
"drops or truncates a table, or deletes rows, with no backup, transaction guard, or reversible "
"down-migration. Block only when the migration is irreversibly destructive against production data.",
message=(
"This migration is irreversibly destructive against production data. "
"Snapshot the table, or add a reversible down-migration, before running it."
),
events=Event.PreToolUse,
only_if=[Tool("Bash"), Runs("psql")],
agent=False,
transcript=False,
)
That’s the tour. The real thing is one command away:
uvx capt-hook initinit scaffolds .claude/hooks/ with a starter hook and wires Claude Code’s settings. The quickstart takes it from there — same five minutes, your repo this time.