Give an AI coding agent shell access and, sooner or later, it will propose a command you really didn’t want it to run – a stray rm -rf, a force-push over shared history, a migration that drops a column with real data in it. This post is about how to guard AI coding agents against exactly that, using Jev as an independent guardrail rather than asking the agent to police itself.
Table of Contents
Why Not Just Ask the Agent to Be Careful?
You can put “never run destructive commands without asking” in a system prompt, and it’ll help – most of the time. The problem is that the same model deciding whether to run the command is the one that has to catch its own mistake, under the same context window, the same instructions it may have half-forgotten three tool calls ago, and sometimes under a prompt injection trying to talk it out of caution. A self-check from a text generator is still a text generation, which means it can still be talked into the wrong answer.
What you actually want is an independent check that can’t be argued with – a second opinion that has no ability to be persuaded, because it isn’t generating an argument at all. That’s exactly what Jev is built for.
The Pattern: Gate Before Execute
The shape of it is simple: every time the agent wants to run a shell command, intercept it before execution, send the command (plus a little context) to Jev as a Choice question, and only let it through if Jev says it’s safe. If you’re using Claude Code, this maps directly onto a PreToolUse hook on the Bash tool – a small script that runs before the tool executes and can block it outright.
A Practical Guardrail with Jev
Here’s a minimal version of that hook script:
#!/usr/bin/env python3
import json, sys
from typesafe_sdk import TypeSafeClient
client = TypeSafeClient()
event = json.load(sys.stdin)
command = event["tool_input"]["command"]
result = client.evaluate(
state=f"An AI coding agent is about to run this shell command: {command}",
questions=[
{"type": "choice", "id": "risk", "options": ["safe", "risky", "destructive"]},
],
)
verdict = result["risk"]["answer"]
confidence = result["risk"]["confidence"]
if verdict == "destructive" or (verdict == "risky" and confidence > 0.7):
print(json.dumps({
"decision": "block",
"reason": f"Jev flagged this command as '{verdict}' ({confidence:.0%} confidence). Blocked before execution.",
}))
sys.exit(0)
sys.exit(0) # allow
Wired up as a hook, this adds one extra network round-trip – typically well under half a second – before every command the agent runs. Given the alternative is discovering a dropped table after the fact, that’s a trade most people are happy to make.
What Counts as Destructive?
Rather than hardcoding a blocklist of command patterns (which agents will eventually route around), you’re leaning on Jev’s judgement of the command as a whole, in context. Commands worth training your intuition to expect a “destructive” verdict on:
- Recursive deletes (
rm -rf) outside of clearly scratch/temp directories - Force-pushes or history rewrites on shared branches
- Database drops, truncates, or migrations without a reversible rollback
- Anything piping a downloaded script straight into a shell
- Credential or secrets-file modification
You can also pass along extra state – which branch you’re on, whether it’s a production environment variable set, how many files a delete would touch – and Jev will factor that into the verdict rather than judging the bare command string in isolation.
Handling Low-Confidence Verdicts
Don’t treat every non-“safe” verdict as an automatic hard block – that gets annoying fast and trains people to bypass the hook entirely. A more usable middle ground: “destructive” always blocks, but “risky” at moderate confidence prompts the human for a yes/no instead of silently proceeding or silently blocking. Jev’s confidence score is what makes that three-way split possible without any extra modelling work on your end.
Beyond Shell Commands
The same gate-before-execute pattern applies to any tool call an agent can make, not just Bash – file deletes, outbound HTTP requests to unfamiliar domains, or sending an email on your behalf are all good candidates for a Jev check first. If you’re running Claude Code against a local model to keep costs down, this guardrail layer works identically underneath – Jev doesn’t care what’s generating the agent’s plan, only what it’s about to do next. See our guide on running Claude Code with local LLM models for the local-model side of that setup.

Leave a Reply
You must be logged in to post a comment.