Back to News & Insights
Artificial Intelligence September 21, 2026 · 6 min read

How to stop AI from confidently shipping broken code (a pattern that actually works)

The scariest diff passed every test — and would've lost a customer money. The scariest diff AI ever...

How to stop AI from confidently shipping broken code (a pattern that actually works)

The scariest diff AI ever handed me passed every test, read beautifully, and would have lost a paying customer their money the first bad night in production. It didn't look risky. It looked finished. That's the whole problem.

AI doesn't ship broken code because it's dumb. It ships broken code because it's confident, and almost nothing in a normal dev loop is trying to prove it wrong. You ask it to write the thing, it writes the thing, the tests go green, and every signal you have says ship. Confidence is the output. Correctness is a coincidence you're hoping rides along.

I spent 30 days letting AI write 100% of my code — refused to type a line of application logic myself — specifically to find out what actually stops this. Not the demos. The real thing, in production, with money on the line. Here's the honest answer: it wasn't a smarter model, and it wasn't a cleverer prompt. It was one structural rule, and you can adopt it today without changing a single tool.

The instinct, when you don't trust the output, is to ask the AI to check its own work:

This is worse than useless, because it feels like a safeguard while doing nothing. An LLM asked "is this correct?" is being asked to agree with itself, and it's exceptionally good at agreeing with itself. It grades its own homework in a slightly more formal voice and hands you back an A. You didn't add a check. You added a second layer of confidence on top of the first one.

The failure isn't that the model is bad at reviewing. It's that "confirm this" and "break this" are different jobs, and confirmation is the one thing the model will always deliver. You have to stop asking for the job you'll always get.

Nothing merges until a second reader — whose only job is to break it — has tried and failed.

Three parts make it actually work. Skip any one and it collapses back into an echo. The reviewer is not the author

The thing that wrote the code cannot be the thing that blesses it — even if it's the same model, it has to be a separate, clean context that never saw the code get written. The author's context is poisoned: it already "knows" the code is right, because it just spent 4,000 tokens convincing itself. A fresh reviewer with no memory of the writing is the cheapest independence you can buy.

Better still: make the reviewer a different model family. Different training distribution, different blind spots. Two models from the same family share the same idea of what "clean" looks like — so they share the same landmines. The single highest-leverage change I made all month was pointing the reviewer at a different model than the author. The reviewer gets a break-it brief, never a bless-it brief

This is the part that does the work. Don't ask the reviewer to review. Give it a job it can only do by finding the failure.

Notice what changed. The reviewer is no longer looking for reasons to say yes. It's hunting a specific, concrete failure, and its default is "this is broken — prove otherwise." Same model. Same weights. Completely different output, because the objective is the reviewer, not the model. A human owns the merge

Not to out-code the machine — it out-codes you. To hold the one thing neither agent has: the memory of having been burned. More on this below, because it's the part that can't be automated and shouldn't be.

Read it. It's clean. It's fast — it acks Stripe immediately so the webhook latency is low. Every test passes, every single time. If you asked an AI "is this correct?" it would say yes and compliment the low-latency acknowledgement. Mine did.

Now run it through the Refutation Gate. The break-it brief says "the database write fails after the external call succeeds." And there it is: it acknowledges the event before it persists it. One database blip in the gap between the 200 and the savePayment — and Stripe believes the event is delivered, your DB never heard about it, and a customer who just paid you has access to nothing and no record they ever paid. On a bad night, silently, to real people.

That bug is invisible to "review this." It is glaringly obvious to "produce the input that loses money." The code didn't change. The question did.

The fix is one line of ordering — persist first, ack after — but you only ever write that fix if something in your loop was actively trying to break the happy path. Confidence would have shipped it. Refutation caught it.

Here's the limit I hit, and I want to be honest about it instead of selling you a fully-automated fantasy.

Want to discuss this further?

Book a free strategy call with our team to see how these insights apply to your specific business goals.

Book a consultation