Precision with Runs
Page one’s token list lowers to Runs, which matches the parsed argv of each command in the line. The other spelling, block_command(r"git\s+stash", ...), keeps a regex over the raw text. That reach is what lets a pattern span a pipe, a redirect, or a flag’s value, and it is also what makes this one block git commit -m "git stash" — a command you actually want to run. Same input, two verdicts, side by side:
from captain_hook import Allow, Block, Event, Input, Runs, hook
hook(
Event.PreToolUse,
"BLOCKED: run git stash through a branch, not directly.",
only_if=[Runs("git", "stash")],
block=True,
tests={
Input(command="git stash"): Block(),
Input(command="sudo git stash"): Allow(),
Input(command='sh -c "git stash"'): Block(),
Input(command="echo git stash"): Allow(),
},
)
Work through the presets and watch the verdicts split. The table shows what the real engine answers — Pattern is the string spelling, Runs is the token spelling page one used, and the widget runs the Runs version (it declines wrapper payloads honestly; capt-hook test covers those for real):
| Command | Pattern | Runs |
|---|---|---|
git stash |
blocks | blocks |
git commit -m "git stash" |
blocks | allows — it’s an argument, not a command |
echo "git stash" |
blocks | allows |
sudo git stash |
blocks | allows — Runs sees sudo’s argv, not what it wraps |
sh -c "git stash" |
blocks | blocks — the parser descends into the payload |
The last two rows are the lesson, and they cut in opposite directions. Runs matches structure, so a wrapper like sudo hides the command from it — cover wrappers with their own entry. But a string payload doesn’t hide anything: the parser descends into sh -c and finds the git stash inside. Precision costs you wrappers, not depth.
So pick by shape, not by nerve. Tokens when you mean a command: block_command(["rm", "-rf"], ...) names one, and a line that only quotes the words never trips it. A string when you mean text no argv prefix names — block_command(r"curl.*\|\s*sh", ...) guards a pipeline, which no token list can describe.