Skip to main content
Bootnode

Catching the Bugs a Normal Review Misses, Before Users Do

Lucho
Lucho
Catching the Bugs a Normal Review Misses, Before Users Do

The app worked.

Tests passed. Every PR had been reviewed. QA had clicked through every flow. By any normal definition, our staking UI was ready.

That's exactly when we decided to put it on trial.

Two AI reviewers read the whole codebase blind, neither one seeing the other's findings. Then a fresh triage had to prove each finding was real before anyone wrote a fix. Then every fix went back to two new judges who had never seen the original review.

Humans drove the whole thing. Every confirmed finding became a GitHub issue, every fix a reviewed pull request, and every write-path change was manually QA'd against a forked mainnet before merging. The AI found; we verified, decided, and shipped.

What it found

Start with the good news: zero critical issues. No path to fund loss, no broken transaction logic. Both judges independently checked the money-handling core and found it clean: stake math, the write-flow state machine, Merkle claim resolution.

The rest of the numbers: the two judges agreed on three findings, seven more came from a single judge, and triage confirmed all of them. Re-judging the fixes turned up five more. Fifteen found, fifteen fixed. A handful of theoretical and cosmetic items we wrote down and left alone on purpose. Knowing what not to fix is part of the job.

All fifteen sit in the same gap: between "the contract call is correct" and "the app never lies to you." A few we liked:

  • One malformed record could black out an entire page. A single validator entry with bad JSON took down the whole validators list. Now the app skips the bad record and renders everything else.
  • The app would let you sign a claim that was guaranteed to fail. We never compared the on-chain Merkle root against the published rewards proof, so a stale proof meant signing a transaction destined to revert: wallet popup, gas spent, error. Now we check first and tell you.
  • A withdrawal that was ready but blocked showed up as "waiting." The queue is FIFO, so a matured withdrawal stuck behind others got labeled as still maturing, with no explanation. Small thing, but exactly what erodes trust in a financial UI.
  • Writes weren't simulated before signing, and a mid-flow network switch could target the wrong chain. Both invisible in happy-path QA. Both fixed.

None of these would sink a protocol. Each one is the difference between an app people trust with their money and one they quietly stop using.

The real lesson: trust the process, not the model

No single participant in this, us included, got everything right.

Alone, each judge saw only part of the picture. Most of the fifteen were caught by one reviewer and missed by the other. And our own fixes weren't exempt: one introduced a new inconsistency, another overreached and would have blocked users from a perfectly legitimate withdrawal. The re-judging round caught both. The second fell when a judge checked the claim against the deployed contract's actual source.

What actually worked was boring: a process that assumes everyone, human or AI, is sometimes wrong. Two independent takes, a triage that demands proof, fresh eyes on every fix.

The compounding win

The fixes shipped as four small, chained pull requests, with behavior tests added along the way. The payoff came after: each kind of finding turned into a check we now run on everything we build. Hermetic tests that don't quietly depend on local config. Simulate-before-sign on every write path. Degraded states that tell users the truth instead of spinning forever.

That's fifteen bugs fixed in one app, and a higher floor for every app after it.

We caught these on a mainnet fork, on purpose. Not on mainnet, by users, with real funds on the line. That order is the whole point.

How we ran it

For the curious, the stack. The whole audit ran on Claude Code, orchestrated by judgment-day, an open-source adversarial-review skill from the gentle-ai project. Each blind judge is a subagent with a fresh context and read-only access: they can't see each other, and they can't see the fix branch. Judgment Day corroborates by agreement; where only one judge flagged something, we verified those findings ourselves in an isolated git worktree against primary sources, from library internals (one was confirmed by reading wagmi's source) to git history and the deployed contract itself. Then every fix went back to two fresh judges. Fixes followed strict TDD with work-unit commits, shipped through our normal issue-first GitHub flow, and write-path changes were manually QA'd on an Anvil mainnet fork before merging.

None of that is exotic, and that's deliberate: the rigor comes from the structure, not from any single tool.

If you're building something where user trust is the product (and in DeFi, it always is), we'd love to compare notes. Reach out at contact@bootnode.dev or ping us on Telegram: @mgarciap.