DEV Community

gentic news
gentic news

Posted on • Originally published at gentic.news

Build an Adversarial Verifier Loop in Claude Code: Catch Bugs Before They Land

Stop trusting Claude Code's self-reports. Add a 3-verifier panel that refutes changes with concrete repro cases, catching bugs tests miss. Capped at 3 rounds.

Key Takeaways

How I’m Using (New) Claude Code LSP to Code & Fix Bugs Faster (Language ...

  • Stop trusting Claude Code's self-reports.
  • Add a 3-verifier panel that refutes changes with concrete repro cases, catching bugs tests miss.

What Changed — The Specific Update

You've been burned by it: Claude Code ships code that looks right, passes tests, but is subtly broken. The author of this Dev.to post built a solution that any Claude Code power user can implement today: an adversarial verification loop.

The core insight: confidence is not correctness. Claude Code's builder agent genuinely believes its work is right — the same way you're blind to your own typos. The fix isn't a smarter model; it's a second agent whose only job is to prove the first one wrong.

What It Means For You — Concrete Impact on Daily Claude Code Usage

This technique catches the failure modes that tests miss:

  • Off-by-one errors that only bite past page 1
  • "Fixed" race conditions that just move the window
  • Refactors that pass existing tests because the tests never cover the changed branch
  • Performance regressions (like clearing cache on every read, causing a 40x slowdown)

The author reports the loop terminates in one or two rounds for most changes. The value isn't endless grinding — it's that ~15% of broken changes get caught before landing, with a concrete repro attached.

Try It Now — Commands, Config, or Prompts to Take Advantage of This

Here's the pattern to implement in your Claude Code workflow:

1. Create a verifier prompt

After your builder agent finishes a change, spawn a verifier with this adversarial prompt:

You are a skeptic. Try to REFUTE this change through the [correctness|security|repro] lens.
Return a concrete failing case. If you cannot prove it is safe, set refuted = true.
Default to REJECT if you can't prove it's safe.
Enter fullscreen mode Exit fullscreen mode

2. Use structured verdicts

Force the verifier to return structured output, not prose:

type Verdict = {
  refuted: boolean;
  failingCase: string | null;  // concrete input/state that breaks it
  lens: "correctness" | "security" | "repro";
  confidence: number;
};
Enter fullscreen mode Exit fullscreen mode

3. Run three verifiers in parallel with distinct lenses

Run one for logic bugs, one for security/input-validation holes, one that tries to reproduce the claimed behavior from scratch. Use majority vote (2 of 3 must reject to trigger a fix round).

4. Cap the loop at 3 rounds

If the builder and skeptics can't agree by round 3, escalate to a human. A loop with no exit condition is just a more expensive way to hang.

Example CLAUDE.md snippet

Add this to your CLAUDE.md to enable the pattern:

## Verification Protocol

After completing any code change, run the adversarial verifier loop:
1. Spawn 3 verifier agents with distinct lenses: correctness, security, repro
2. Each verifier must return a structured verdict with a concrete failing case
3. If 2+ verifiers refute the change, fix and re-verify (max 3 rounds)
4. If unresolved after 3 rounds, flag for human review
Enter fullscreen mode Exit fullscreen mode

Pro tip: Reject with a repro, not an opinion

A verdict of "this looks risky" is useless. A verdict of failingCase: "empty array → throws on line 12" is a test case. When rejection carries a concrete failing input, the builder's next attempt is grounded instead of guessing.


Source: dev.to

[Updated 09 Jul via devto_claudecode]

The original post also includes a flowchart LR diagram (Builder → Verifier agents → majority reject loops back to Builder, majority accept leads to Merge) that visually clarifies the iteration logic. The author notes that the one failure mode that finally drove them to build this system was a caching bug fix that cleared cache on every read, causing a ~40x slowdown on a hot path — a regression that tests never caught because no assertion looked for performance [per dev.to].


Originally published on gentic.news

Top comments (0)