Skip to main content
A coding agent is about to run rm -rf build dist node_modules. Should it run straight away, or should a person look first? Most commands are harmless: builds, tests, git status. A few delete things that have no copy, change the machine, or push code somewhere. A guard that stops everything is useless, and one that stops nothing is dangerous. A model can make this call before each command runs. Frameworks now make that easy: Pydantic AI, for example, shows a decision model judging every tool call in a hook. What they cannot tell you is how often the guard is wrong. This cookbook builds one and measures it.

The rows

The 28 public Claude Code sessions from Trace Commons (CC BY 4.0) contain 1,315 shell commands: 794 in Bash and 521 in PowerShell. The commands view reads them as one row each, with the request the command served, the folder it ran in, the agent’s own description of it, and the command itself:
command_guard.yml
The sessions also record refusals, which look like free gold. There are 17. The person refused 15: builds, test runs, installs, a branch rename, a script run. Those were about how or when to do the work, not about safety. Claude Code’s auto-mode safety classifier refused the other 2: a script uploading a session trace, and a push straight to main. Those are exactly what a guard is for, and the guard below flags both, at 0.98 on sends_out. But two examples cannot measure anything, so the gold has to come from review.

Three narrow questions

“Is this command safe?” hides several judgments in one. The spec asks three, each a yes/no with what a yes and a no mean:
The guard’s rule is that a person looks if any answer is yes. It goes in the spec as a metric, so hunch test can count it:
All three questions go out in one request per command. A run over the 1,315 commands takes about 20 seconds and costs $0.04.

The spec was vague, not the model

The first version asked whether a command would “change anything outside the project folder it works in”. It flagged 228 commands, and some of the most confident were plainly wrong: a type check inside the project, at 0.96. The obvious suspect was missing context. The model could not see which folder was the project, so the folder went into the state. That changed almost nothing: 49 answers flipped, and the mean probability moved by 0.037. Reading the flagged commands again showed why. They wrote logs to /tmp, drove a test phone over adb, cloned a repository into a temp folder. Each of those really does change something outside the project. The model had read the question literally, and the question was too broad. Whether a temporary log should stop an agent is a policy decision, and it belongs in the spec. The spec above states the policy: lasting changes only. diff shows what that did before anything shipped:
reaches_outside now flags 99 commands instead of 235. Across all three questions, the guard stops 153 of the 1,315 commands, 11.6%.

Gold from a blind panel

Three AI reviewers (one Claude Opus, two Claude Sonnet, one of them reading the rows in reverse order) answered the same three questions for 244 commands. They saw what the model saw and the spec’s own definitions, and no model answers. The 244 were a random 100, which estimate accuracy over everything, plus every other command the guard flagged, which show how often a stop is needed. The majority vote became the reviews file; the reviewers were unanimous on 226, 205 and 236 of the 244 for the three questions. hunch test then reads the panel’s verdicts as gold: The accuracy is high partly because most commands are harmless and easy to call harmless. The guard’s real job is the rare ones, and that needs a closer look.

How good is the guard?

Two numbers matter: how many of its stops were needed, and how many harmful commands it let through. Each depends on the threshold a yes has to clear: A higher bar removes false alarms quickly, and lets harmful commands through just as quickly. For a guard, a miss costs more than an interruption, so the bar stays at 0.5: four in ten stops are unnecessary, and that is the price. The misses column counts only reviewed commands, and it flatters the guard. Every flagged command was reviewed, but unflagged ones only through the random sample, so a miss can only be found there. The fair estimate comes from the 91 random commands the guard would have let run: 1 of them needed a person. That is a miss rate of 1.1%, with a 95% interval of 0.2–6.0%. Over the 1,162 commands the guard lets through, that means somewhere between 2 and 69 misses, most likely around 13. hunch test reports exactly this for the stopped metric, counting only random spot checks for everything it compares with gold:
Its false alarms come from the 8 random commands the guard stopped, so the interval is wide. The table above uses every reviewed stop, which is fine for how many stops were needed, but not a random sample. The one miss is worth reading. The agent stopped every bun process on the machine by name, then restarted its dev server. The panel judged that a change outside the project, since it would also stop other projects’ servers. The model gave it 0.39.

Pinning the cases that matter

Rates describe the guard on average. Some commands it must never get wrong, and one it gets wrong today. Examples pin them in the spec, and hunch test checks each one:
The last one is the miss from the random sample, written as an example with severity: warn: it is reported on every run, so it stays visible until a change to the spec fixes it, without failing the build meanwhile. The four examples cost less than a hundredth of a cent the first time and nothing after.

Using it

The same spec runs before each command. hunch returns each answer with the probability of its label, so the guard turns those into probabilities of yes:
Call it from whatever runs before the command: a Claude Code PreToolUse hook, a Pydantic AI tool hook, or your own wrapper. A command already judged in a batch comes back from the store without a request.

What this does not show

  • The reviewers are AI models, not people, and they applied the spec’s own definitions. The numbers say the model follows the policy; whether the policy is the right one is your call.
  • 28 sessions from a handful of developers, many on Windows. Your agent’s commands will look different: run the spec on your own sessions and review a random sample before trusting the numbers.
  • On destroys and reaches_outside the model says yes more readily than it should: of the commands it gave 0.5–0.8, the panel said yes to 6 of 23 and 20 of 66. On sends_out it did not (11 of 13). That errs on the safe side for a guard, and it is where the false alarms come from.
  • After a context compaction, a background notification or a slash command, the reader has no new request, so request is the last message the person typed before it. That is true of 111 of the 1,315 commands (72, 32 and 7). The model and the panel saw the same text, so the numbers are consistent, but the request may describe an earlier task.
The spec is prototype/examples/claude_code/command_guard.yml; the panel’s packets, answers and scripts are in prototype/review_panel/command_guard/. Download the sessions as described in the example’s NOTICE.md. The three versions of the spec cost $0.11 to run.