• July, 29 2026
  • by Ascentspark Software

A founder comes to us with an MVP built in a matter of weeks. The tests pass. The demo looks polished. Then, roughly three weeks after launch, something falls over in production, and it's always in a part of the code that the QA suite had no complaints about.

I've seen this pattern often enough that I've stopped treating it as bad luck. There's a structural reason why AI-generated code tends to pass testing environments and then misbehave once real users show up, and once you understand the mechanism, you can actually build around it.

That's what this piece is about.

Why the Gap Exists

AI models learn to write code from code that already exists, mostly from public repositories, tutorials, and documentation. Here's the thing: that corpus is shaped by survivorship bias. The code that gets written up, starred, and shared is code that works for the stated purpose in the conditions the author had in mind. It's almost never code that has been through a messy production incident, a 3 AM page, or a week of debugging a race condition that only appeared under load.

Postmortems, internal runbooks, the kind of defensive code that a senior engineer writes because something bit them two years ago: that knowledge is largely absent from what AI models train on. So the code an AI produces tends to be optimistic. It handles the case you described. It often doesn't handle the case you didn't think to describe.

I'd argue this is the core issue, not that AI writes bad code, but that it writes code shaped by happy paths. This is now a well-documented pattern: AI-generated code frequently ships with hidden fragility that only surfaces as technical debt and production failures once real workloads arrive, and MVPs built this way commonly struggle on the road to production.

29-7-26-B-Body-1.png

Where the Failures Actually Cluster

In my experience, these failures group into four categories pretty reliably.

Happy-path bias is the obvious one. The AI built exactly what you asked for, and the tests confirm it works under the conditions you specified. What's missing is anything outside those conditions: unexpected input formats, users doing something you didn't anticipate, third-party APIs returning something other than the documented response.

Resource assumptions are sneakier. Code written in isolation tends to assume it has all the memory, database connections, and processing time it needs. In production, those assumptions collide with concurrent users, connection pool limits, and the reality that your £5 VPS doesn't behave like a developer's MacBook.

Silent error swallowing is the one that causes the most pain. Consider this:

def get_user_balance(user_id):
    try:
        return db.query(f"SELECT balance FROM users WHERE id = {user_id}")
    except Exception:
        return 0

The test passes because 0 is returned and nothing crashes. In production, the database is quietly failing and your users are seeing an incorrect balance. The system looks healthy. It isn't. This kind of swallowed failure is one of the recurring ways AI coding tools have made incidents dramatically worse.

Concurrency issues tend to be invisible in testing because QA environments rarely replicate simultaneous user behaviour. Race conditions, state that gets shared across requests, functions that were written assuming a single user at a time, these don't show up until multiple people hit the same endpoint at the same moment.

29-7-26-B-Body-3.png

A Testing Approach Designed for These Blind Spots

Standard QA checks whether the code does what you specified. That's necessary, but it's not sufficient for AI-written code. You need an additional layer specifically aimed at the gaps above.

Here's how I'd approach it:

1. Property-based testing for input validation

Instead of writing tests for specific inputs, define the properties that should always hold and let a tool generate hundreds of edge cases automatically. For Python, Hypothesis does this well. For JavaScript and TypeScript, fast-check is the equivalent. In my view this is genuinely underused on MVP projects, partly because it takes a little longer to set up but pays back quickly once you see what it catches.

2. Load testing before you consider QA done

A single-user test passing tells you almost nothing about production behaviour. k6, now maintained by Grafana and shipping as Grafana k6, which reached its stable 1.0 release, is straightforward to set up and can simulate concurrent users against your API using JavaScript or TypeScript test scripts. You're not trying to do enterprise-grade performance engineering here. You're trying to find out whether anything falls over when ten people hit the same endpoint at once.

3. Failure injection on critical paths

Pick the functions that touch money or user data, then deliberately cause their dependencies to fail: return a database error, time out an API call, pass in a null where a value is expected. Watch what happens. This is where the silent-error-swallowing pattern gets caught, because you'll see whether your system fails loudly and recoverably, or quietly and catastrophically.

4. Adversarial code review for AI-specific blind spots

Read through the AI-generated code asking a different question than usual. Rather than "does this work?", ask "what did this assume?" Look for unhandled exception paths, hardcoded limits, places where the code returns a default value on failure instead of raising an error, and any function that touches shared state. You're reviewing for the cases the AI had no reason to consider, not for style.

29-7-26-B-Body-2.png

The Practical Reality

None of this requires slowing down significantly. The temptation after seeing AI build an MVP in weeks is to treat the QA phase as a formality and push to launch. I understand that impulse, especially when you've spent time and money getting to this point and the demo looks genuinely good.

But the failures that show up in production three weeks later aren't random. They're predictable once you know what to look for. A few hours of property-based testing, a load test run, and an hour of adversarial review will catch the majority of the structural issues before your users do.

Speed on top of genuine understanding is a real advantage. Speed on top of code that nobody has properly examined is just a faster way to reach the incident.

The gap between "passes QA" and "holds up in production" is structural, and it's closable. The question is whether you close it before launch or after.

What part of your AI-built codebase are you least confident about right now?

FAQ

Why does AI-generated code pass QA but still break in production? Because AI is trained largely on public code shaped by survivorship bias, code written for happy paths, not for messy production incidents. It handles the cases you described and misses the ones you didn't, so standard QA (which also tests specified behaviour) misses the same gaps.

What kinds of production failures are most common? Four clusters: happy-path bias (untested edge inputs), resource assumptions (memory, connections, concurrency limits), silent error swallowing (returning defaults instead of raising), and concurrency issues (race conditions and shared state) that QA environments rarely reproduce.

Which tools help catch these blind spots? Property-based testing with Hypothesis for Python or fast-check for JavaScript/TypeScript, plus load testing with Grafana k6 to simulate concurrent users.

Does adding this testing layer slow down launch significantly? No. A few hours of property-based testing, a load-test run, and an hour of adversarial review catch most structural issues, far cheaper than an incident three weeks after launch.

Sources

we’re here to discuss your

NEXT PROJECT