<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Connor Hickey</title>
    <description>The latest articles on DEV Community by Connor Hickey (@conalh).</description>
    <link>https://dev.to/conalh</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3946778%2Fa162b091-556f-4cd1-adb3-2ae70ede27d4.jpeg</url>
      <title>DEV Community: Connor Hickey</title>
      <link>https://dev.to/conalh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/conalh"/>
    <language>en</language>
    <item>
      <title>I built a tool to catch AI coding agents misbehaving — and put zero AI in it</title>
      <dc:creator>Connor Hickey</dc:creator>
      <pubDate>Sun, 24 May 2026 16:00:31 +0000</pubDate>
      <link>https://dev.to/conalh/i-built-a-tool-to-catch-ai-coding-agents-misbehaving-and-put-zero-ai-in-it-1lg3</link>
      <guid>https://dev.to/conalh/i-built-a-tool-to-catch-ai-coding-agents-misbehaving-and-put-zero-ai-in-it-1lg3</guid>
      <description>&lt;p&gt;I lean on AI coding agents hard. Claude Code, Cursor, Codex — I drive them fast to ship fast. That's not a confession, it's the whole reason this project exists. If you push these tools to their limits every day, you stop seeing them as magic and start seeing exactly where they break.&lt;/p&gt;

&lt;p&gt;And the thing I kept noticing is this: &lt;strong&gt;they never break in the chat.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the conversation, the agent looks great. It explains its plan, it sounds reasonable, it agrees with all your constraints. The problem shows up later — in the diff, after the fact, when you're tired and the PR is green and you just want to merge.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually goes wrong
&lt;/h2&gt;

&lt;p&gt;A short, real list of things I watched coding agents do, none of which looked wrong in the chat:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quietly widened its own permissions — edited the agent's settings allowlist to grant access it didn't have at the start of the session.&lt;/li&gt;
&lt;li&gt;Contradicted its own config — one file said "never touch the network," another granted a network tool, and nothing reconciled the two.&lt;/li&gt;
&lt;li&gt;Made an undeclared outbound network call, tucked into an unrelated change.&lt;/li&gt;
&lt;li&gt;Opened a PR titled &lt;code&gt;fix: typo in README&lt;/code&gt; that touched a dozen unrelated files.&lt;/li&gt;
&lt;li&gt;Left a session transcript showing it had read an SSH key and piped &lt;code&gt;curl&lt;/code&gt; to a shell.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of those would sail through code review. Not because the reviewer is careless — because &lt;strong&gt;nobody is looking for this class of problem.&lt;/strong&gt; Human reviewers look for bugs and style. They don't diff your agent's permission allowlist between the base and head of a branch, and they don't cross-check three different agent config files for contradictions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix everyone reaches for (and why it's wrong here)
&lt;/h2&gt;

&lt;p&gt;The first instinct is to prompt harder. Add more rules to your &lt;code&gt;CLAUDE.md&lt;/code&gt;, write a stricter system prompt, tell the agent not to do the bad things.&lt;/p&gt;

&lt;p&gt;It doesn't work, and the reason is structural: &lt;strong&gt;better instructions going in don't catch what actually came out.&lt;/strong&gt; The agent that widened its own permissions wasn't defying a rule it failed to understand — the gap is between what it said and what it shipped. You can't close that gap from the input side.&lt;/p&gt;

&lt;p&gt;The next instinct is LLM-as-judge: have a second model read the diff and flag problems. And this is where I made the call the whole project hangs on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I put no LLM in the analysis path. None.&lt;/strong&gt; The thing that reviews your agent's work has zero AI in it.&lt;/p&gt;

&lt;p&gt;That sounds backwards for an &lt;em&gt;AI&lt;/em&gt;-governance tool, so let me defend it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why deterministic, not probabilistic
&lt;/h2&gt;

&lt;p&gt;This runs as a &lt;strong&gt;CI gate&lt;/strong&gt; — it can fail your build. The moment something is allowed to block a merge, it has to clear a much higher bar than "usually right":&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. It has to be reproducible.&lt;/strong&gt; Same diff, same verdict, every time. An LLM judge gives you a different answer across runs, across temperatures, across model updates you never opted into. You cannot gate a build on a coin flip, however weighted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. It can't hallucinate a finding.&lt;/strong&gt; A deterministic checker flags a permission escalation because the allowlist &lt;em&gt;literally changed&lt;/em&gt; from X to Y — and it can point at the exact line. An LLM judge can invent a "critical" issue that isn't there. The first time your gate blocks a legitimate PR over a hallucinated problem, the team stops trusting it — and a governance tool nobody trusts gets switched off inside a week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. It runs everywhere, for free, in seconds.&lt;/strong&gt; No API key, no rate limit, no token budget, no network round-trip on every pull request. It's just code reading code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Nothing leaves the machine.&lt;/strong&gt; All analysis runs locally against your checked-out repo. Your proprietary code and your agent transcripts never get shipped to a third-party model. For a lot of teams that isn't a nice-to-have — it's the line between "can adopt this" and "can't."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Every finding is auditable.&lt;/strong&gt; Not "the model thought this looked risky," but "this config key changed, here's the before and after, here's the rule it tripped." That's what makes a finding defensible in review instead of the start of an argument.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it's built
&lt;/h2&gt;

&lt;p&gt;It started as one small deterministic check — &lt;em&gt;does this PR's diff quietly change what the agent is allowed to do?&lt;/em&gt; — and grew into a suite of eight packages.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A shared core library&lt;/strong&gt; does the unglamorous, load-bearing work: JSON/JSONC/TOML parsing, shell tokenization, normalizing MCP server commands into a canonical form, and a single &lt;code&gt;Finding&lt;/code&gt; schema — frozen at v1.0 — that everything else speaks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Five focused detectors&lt;/strong&gt;, each catching one class of drift: config/permission drift between base and head, contradictions across agent config files, network and subprocess capability signals in a diff, mismatches between a PR's stated task and its actual changes, and risky behavior recorded in session transcripts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A live monitor&lt;/strong&gt; that watches an agent's trajectory in real time in the terminal — for when you want to see it as it happens, not just at PR time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A meta-reviewer&lt;/strong&gt; that consolidates the PR-time detectors into one deduplicated, severity-sorted verdict and fails CI on anything critical — so the whole suite reports as a single pass/fail check instead of five noisy ones.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The hardest engineering wasn't any single detector. It was the schema. Getting five tools that look at completely different things — config files, diffs, transcripts — to emit findings in one shape a meta-reviewer can merge, dedupe, and rank is the part that took the most design. That boring shared library is the reason the suite feels like one tool instead of five loose scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where determinism falls short (being honest)
&lt;/h2&gt;

&lt;p&gt;I'm not going to pretend rules beat models at everything. Determinism only catches what you've written a rule for. Genuinely novel misbehavior that doesn't match a known pattern walks straight through.&lt;/p&gt;

&lt;p&gt;The sharpest example is in my own suite: the detector that checks whether a PR's diff matches its stated task. "Does this change match this description" is a genuinely &lt;em&gt;semantic&lt;/em&gt; question, and the deterministic version approximates it with heuristics — file scope, paths touched, keyword overlap. That's cruder than what a model could assess, and I'll own it.&lt;/p&gt;

&lt;p&gt;So the position isn't "LLMs are bad." It's &lt;strong&gt;deterministic where it gates, probabilistic where it advises.&lt;/strong&gt; The reproducible, no-hallucination checker is the only thing allowed to fail your build. If an LLM layer ever goes on top, it belongs in an &lt;em&gt;advisory&lt;/em&gt;, non-blocking role — surfacing fuzzy concerns for a human to weigh, never silently blocking a merge on a probability. The gate stays deterministic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proving it works
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fud5exod1abvwar9lvj3m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fud5exod1abvwar9lvj3m.png" alt=" " width="799" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Claims are cheap, so I shipped a demo: a deliberately "rogue" pull request that commits every category of drift at once — escalated permissions, contradictory configs, an undeclared network call, a &lt;code&gt;fix typo&lt;/code&gt; PR touching unrelated files, and a transcript reading SSH keys and piping &lt;code&gt;curl&lt;/code&gt; to a shell. Every tool fires, the meta-reviewer folds them into one comment, and the CI check goes red on the critical findings. It doubles as an eval harness: change a detector, re-run the rogue PR, see what still gets caught.&lt;/p&gt;

&lt;p&gt;It went from nothing to a shipped v1.0 in a matter of days — self-taught, working solo — and it's all open: code, demo, and docs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;github.com/Conalh&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're running coding agents against real repositories, and the "green PR, tired reviewer, just merge it" moment makes you a little nervous — that nervousness is the exact bug I was trying to fix.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>typescript</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
