---
title: "QA for AI-Generated Code"
canonical: https://wavect.io/blog/qa-for-ai-generated-code/
language: en
description: "What breaks in AI-generated code from Lovable, Cursor, Claude Code, and Replit before launch, and the production-readiness checklist Wavect runs to catch it."
image: "https://wavect.io/img/blog/headers/header_qa-for-ai-generated-code.png"
---

[**Back**](/blog/overview/)

[![Christof Jori](/img/team/christof.webp)](/team/christof-jori/)

[Christof Jori](/team/christof-jori/) https://linkedin.com/company/wavect

8 min read · 08 Jun 2026

[**Next**](/blog/why-ai-agent-projects-get-cancelled/)

# QA for AI-Generated Code: What Breaks Before Launch and How to Catch It

TL;DR

AI-generated code from Lovable, Cursor, Claude Code, or Replit demos cleanly and breaks predictably: missing authorization checks, unvalidated input, secrets in client-side code, and no error handling cluster on every build. The single most common serious defect is a working login screen with no server-side check stopping user A from reading user B's data. The post is the QA process Wavect runs on AI-assisted builds before they go live.

Related service: [Vibe Coding Rescue](/services/vibe-coding-rescue/)

An AI prototype from Lovable, Cursor, Claude Code, or Replit gets you to a working demo in a weekend. It does not get you to production. The gap between "it works on my screen" and "it survives real users, real load, and a security review" is where AI-generated code quietly fails. This is the QA process we run on AI-assisted builds before they go live, and the failure modes we see most.

None of this is an argument against building with AI. We build with it too. It is an argument for testing the output the same way you would test any code that is about to touch real money, real data, and real users.

## Why does AI-generated code break in production?

AI coding tools optimise for one thing: producing something that runs and matches the prompt. They do not optimise for the things that decide whether software survives contact with users. The model has no view of your threat model, your data volumes, your edge cases, or your compliance obligations. It writes the happy path well and skips almost everything else, because nobody asked.

The result is code that demos cleanly and breaks predictably. The breakages are not random. They cluster in the same places every time, which is what makes them testable.

## What actually breaks in AI-generated code?

Here is the list we work through on every AI-assisted build, ordered by how often it bites.

- **Authentication and authorization gaps.** The login screen works. The check that stops user A from reading user B's data is missing or applied on the frontend only. This is the single most common serious defect we find.
- **Input that is never validated.** Forms accept anything. No length limits, no type checks, no sanitisation. The demo data was clean, so the gap never showed.
- **Secrets in the wrong place.** API keys, database URLs, and tokens hardcoded in client-side code or committed to the repo. AI tools paste them inline because it makes the example run.
- **No error handling.** The happy path is covered. A failed network call, a timeout, or an empty result throws an unhandled exception and the screen goes blank.
- **Queries that do not scale.** Code that loops a database call inside a render, or pulls a whole table to count rows. Fine with 10 records, fatal with 100,000.
- **Race conditions and double submits.** Two clicks create two orders. Two parallel requests both pass a balance check and both withdraw.
- **Dependencies nobody vetted.** The model pulls in packages that are outdated, abandoned, or carry known vulnerabilities.
- **State that lies.** The UI says the payment succeeded; the backend never recorded it. Optimistic updates with no reconciliation.

![Christof Jori](/img/team/christof.webp)

"AI does not write insecure code on purpose. It writes the code you asked for and nothing you forgot to ask for. Production is the sum of everything you forgot to ask for."

## The production-readiness checklist for AI-assisted builds

This is the structure of a Wavect review. You can run a first pass yourself before you call anyone.

1. **Authorization audit.** For every endpoint and every data read, confirm the server checks who is asking and whether they are allowed. Frontend checks do not count.
2. **Input boundary test.** Throw malformed, oversized, and hostile input at every entry point. Confirm it is rejected cleanly, not absorbed.
3. **Secret sweep.** Scan the repo and the client bundle for keys, tokens, and credentials. Rotate anything that leaked and move it server-side.
4. **Failure-path coverage.** Force every external call to fail and confirm the app degrades gracefully instead of crashing.
5. **Load and query review.** Profile the database under realistic data volumes. Kill N+1 queries and unbounded reads before they kill you.
6. **Concurrency test.** Fire parallel and duplicate requests at anything that writes money or state. Add idempotency where it is missing.
7. **Dependency and licence scan.** Check every package for known vulnerabilities and incompatible licences.
8. **Regression suite.** Write the tests the prototype never had, so the next AI-assisted change does not silently break what already works. See [test-driven development](/glossary/tdd/) for why this matters more, not less, when AI is writing the code.

This is the core of our [software QA service](/services/software-quality-assurance/). The deliverable is not a PDF of complaints. It is a fixed, tested codebase and the test suite that keeps it fixed.

## Can I just ask the AI to fix its own code?

Partly. An AI tool will happily add a validation check or wrap a call in error handling once you point at the spot. What it cannot do is decide where to look. It has no model of your [technical debt](/glossary/technical-debt/), no memory of the order in which things were built, and no instinct for the edge case a real user will hit on day two. Finding the gaps is human work. Closing them, increasingly, is shared work. That split is exactly how we run these engagements. For one narrow discovery step, our [Cisco Antares review explains local CWE-driven vulnerability localization](/blog/cisco-antares-local-vulnerability-localization/) and why its candidate files still require a security reviewer.

## How long does it take to make AI-generated code production-ready?

For a typical vibe-coded MVP, a focused review and hardening pass runs one to three weeks. The variance is driven by two things: how much real money or sensitive data the product touches, and how far the AI ran without supervision. A weekend prototype that handles payments and personal data needs more than a weekend of QA. A read-only internal tool needs far less. We scope it after a first look, not before.

## When is the code beyond saving?

Rarely, but it happens. If the data model is fundamentally wrong, or the same broken pattern is copied across a hundred files, rebuilding the core is cheaper than patching it. We will tell you that on the first call rather than bill you for a month of patching a foundation that needs to be poured again. Honesty here is cheaper for everyone.

## Final thoughts

AI-generated code is not worse code. It is unreviewed code. The prototype that took a weekend skipped the same weeks of hardening that every production system needs, and the bill for those weeks does not disappear because a model wrote the first draft. It just moves to launch day, when it is most expensive.

Run the checklist before you put real users in front of an AI-assisted build. If the authorization, input, and failure-path sections make you nervous, that is the signal to get a second set of eyes on it before launch, not after the incident.

## You may also like..

[**External QA Benchmark: The First 30 Days** What published defect research supports, which QA metrics are comparable, and why no honest universal bug quota exists.](/blog/external-qa-benchmark-first-30-days/) [**Wavect vs Dotbite** A fair comparison of two Austrian software shops, and which one fits a founder-led AI build versus a Vienna custom-software project.](/compare/wavect-vs-dotbite/)

QA and production readiness

## Continue through this cluster

- [External QA Benchmark: What We Find in the First 30 Days](/blog/external-qa-benchmark-first-30-days/)
- [What Software Maintenance Costs After Launch: A DACH SaaS Benchmark](/blog/software-maintenance-cost-benchmark-dach-saas/)
- [Agile De-engineering](/blog/agile-de-engineering/)
- [Lovable, Bolt, and Replit App Due Diligence](/blog/lovable-bolt-replit-app-due-diligence/)
- [The Vibe-Code Production-Readiness Checklist](/blog/vibe-code-production-readiness-checklist/)

Inbox, without the noise

## Follow the work that matters to you

Get a short email when we publish something new. Follow the whole blog or only the problems you care about.

[**Back**](/blog/overview/)

[![Christof Jori](/img/team/christof.webp)](/team/christof-jori/)

[Christof Jori](/team/christof-jori/) https://linkedin.com/company/wavect

8 min read · 08 Jun 2026

[**Next**](/blog/why-ai-agent-projects-get-cancelled/)

New posts by email ×

×

Get new posts by email

A short email when we publish. Free, no tracking.

## Structured Data

```json
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@id": "https://wavect.io/#organization",
      "@type": [
        "Organization",
        "ProfessionalService",
        "LocalBusiness"
      ],
      "employee": [
        {
          "@id": "https://wavect.io/team/kevin-riedl/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Kevin Riedl",
          "url": "https://wavect.io/team/kevin-riedl/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        },
        {
          "@id": "https://wavect.io/team/christof-jori/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Christof Jori",
          "url": "https://wavect.io/team/christof-jori/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        }
      ],
      "founder": [
        {
          "@id": "https://wavect.io/team/kevin-riedl/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Kevin Riedl",
          "url": "https://wavect.io/team/kevin-riedl/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        },
        {
          "@id": "https://wavect.io/team/christof-jori/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Christof Jori",
          "url": "https://wavect.io/team/christof-jori/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        }
      ],
      "legalRepresentative": [
        {
          "@id": "https://wavect.io/team/kevin-riedl/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Kevin Riedl",
          "url": "https://wavect.io/team/kevin-riedl/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        },
        {
          "@id": "https://wavect.io/team/christof-jori/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Christof Jori",
          "url": "https://wavect.io/team/christof-jori/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        }
      ],
      "name": "Wavect GmbH",
      "subjectOf": {
        "@id": "https://wavect.io/verified-claims.json#dataset",
        "@type": "Dataset",
        "creator": {
          "@id": "https://wavect.io/#organization",
          "@type": [
            "Organization",
            "ProfessionalService",
            "LocalBusiness"
          ]
        },
        "description": "A machine-readable registry of quantitative and qualitative claims published by Wavect, with review dates, localized page appearances and public third-party citations where available.",
        "inLanguage": "en",
        "isAccessibleForFree": true,
        "license": "https://creativecommons.org/licenses/by/4.0/",
        "name": "Wavect verified publication claims",
        "url": "https://wavect.io/verified-claims.json"
      },
      "url": "https://wavect.io/"
    },
    {
      "@id": "https://wavect.io/team/kevin-riedl/#person",
      "@type": "Person",
      "jobTitle": "Managing Director",
      "name": "Kevin Riedl",
      "sameAs": [
        "https://www.wikidata.org/wiki/Q139796365",
        "https://www.linkedin.com/in/wsdt",
        "https://github.com/wsdt"
      ],
      "url": "https://wavect.io/team/kevin-riedl/",
      "worksFor": {
        "@id": "https://wavect.io/#organization",
        "@type": [
          "Organization",
          "ProfessionalService",
          "LocalBusiness"
        ]
      }
    },
    {
      "@id": "https://wavect.io/team/christof-jori/#person",
      "@type": "Person",
      "jobTitle": "Managing Director",
      "name": "Christof Jori",
      "sameAs": [
        "https://www.wikidata.org/wiki/Q139796367",
        "https://www.linkedin.com/in/jocr77/",
        "https://github.com/jo-chris"
      ],
      "url": "https://wavect.io/team/christof-jori/",
      "worksFor": {
        "@id": "https://wavect.io/#organization",
        "@type": [
          "Organization",
          "ProfessionalService",
          "LocalBusiness"
        ]
      }
    },
    {
      "@id": "https://wavect.io/#website",
      "@type": "WebSite",
      "inLanguage": [
        "en",
        "de",
        "es",
        "zh"
      ],
      "name": "Wavect",
      "potentialAction": {
        "@type": "SearchAction",
        "query-input": "required name=search_term_string",
        "target": {
          "@type": "EntryPoint",
          "urlTemplate": "https://wavect.io/search/?q={search_term_string}"
        }
      },
      "publisher": {
        "@id": "https://wavect.io/#organization",
        "@type": [
          "Organization",
          "ProfessionalService",
          "LocalBusiness"
        ]
      },
      "url": "https://wavect.io/"
    },
    {
      "@id": "https://wavect.io/blog/qa-for-ai-generated-code/#webpage",
      "@type": "WebPage",
      "dateModified": "2026-07-07",
      "inLanguage": "en",
      "isPartOf": {
        "@id": "https://wavect.io/#website",
        "@type": "WebSite"
      },
      "lastReviewed": "2026-07-07",
      "url": "https://wavect.io/blog/qa-for-ai-generated-code/"
    }
  ]
}
```

```json
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "abstract": "AI-generated code from Lovable, Cursor, Claude Code, or Replit demos cleanly and breaks predictably: missing authorization checks, unvalidated input, secrets in client-side code, and no error handling cluster on every build. The single most common serious defect is a working login screen with no server-side check stopping user A from reading user B's data. The post is the QA process Wavect runs on AI-assisted builds before they go live.",
  "articleBody": " Blog overview/Delivery and QA/QA and production readiness QA for AI-Generated Code: What Breaks Before Launch and How to Catch It TL;DR AI-generated code from Lovable, Cursor, Claude Code, or Replit demos cleanly and breaks predictably: missing authorization checks, unvalidated input, secrets in client-side code, and no error handling cluster on every build. The single most common serious defect is a working login screen with no server-side check stopping user A from reading user B's data. The post is the QA process Wavect runs on AI-assisted builds before they go live. Related service: Vibe Coding Rescue An AI prototype from Lovable, Cursor, Claude Code, or Replit gets you to a working demo in a weekend. It does not get you to production. The gap between \"it works on my screen\" and \"it survives real users, real load, and a security review\" is where AI-generated code quietly fails. This is the QA process we run on AI-assisted builds before they go live, and the failure modes we see most. None of this is an argument against building with AI. We build with it too. It is an argument for testing the output the same way you would test any code that is about to touch real money, real data, and real users. Why does AI-generated code break in production? AI coding tools optimise for one thing: producing something that runs and matches the prompt. They do not optimise for the things that decide whether software survives contact with users. The model has no view of your threat model, your data volumes, your edge cases, or your compliance obligations. It writes the happy path well and skips almost everything else, because nobody asked. The result is code that demos cleanly and breaks predictably. The breakages are not random. They cluster in the same places every time, which is what makes them testable. What actually breaks in AI-generated code? Here is the list we work through on every AI-assisted build, ordered by how often it bites. Authentication and authorization gaps. The login screen works. The check that stops user A from reading user B's data is missing or applied on the frontend only. This is the single most common serious defect we find. Input that is never validated. Forms accept anything. No length limits, no type checks, no sanitisation. The demo data was clean, so the gap never showed. Secrets in the wrong place. API keys, database URLs, and tokens hardcoded in client-side code or committed to the repo. AI tools paste them inline because it makes the example run. No error handling. The happy path is covered. A failed network call, a timeout, or an empty result throws an unhandled exception and the screen goes blank. Queries that do not scale. Code that loops a database call inside a render, or pulls a whole table to count rows. Fine with 10 records, fatal with 100,000. Race conditions and double submits. Two clicks create two orders. Two parallel requests both pass a balance check and both withdraw. Dependencies nobody vetted. The model pulls in packages that are outdated, abandoned, or carry known vulnerabilities. State that lies. The UI says the payment succeeded; the backend never recorded it. Optimistic updates with no reconciliation. \"AI does not write insecure code on purpose. It writes the code you asked for and nothing you forgot to ask for. Production is the sum of everything you forgot to ask for.\" The production-readiness checklist for AI-assisted builds This is the structure of a Wavect review. You can run a first pass yourself before you call anyone. Authorization audit. For every endpoint and every data read, confirm the server checks who is asking and whether they are allowed. Frontend checks do not count. Input boundary test. Throw malformed, oversized, and hostile input at every entry point. Confirm it is rejected cleanly, not absorbed. Secret sweep. Scan the repo and the client bundle for keys, tokens, and credentials. Rotate anything that leaked and move it server-side. Failure-path coverage. Force every external call to fail and confirm the app degrades gracefully instead of crashing. Load and query review. Profile the database under realistic data volumes. Kill N+1 queries and unbounded reads before they kill you. Concurrency test. Fire parallel and duplicate requests at anything that writes money or state. Add idempotency where it is missing. Dependency and licence scan. Check every package for known vulnerabilities and incompatible licences. Regression suite. Write the tests the prototype never had, so the next AI-assisted change does not silently break what already works. See test-driven development for why this matters more, not less, when AI is writing the code. This is the core of our software QA service. The deliverable is not a PDF of complaints. It is a fixed, tested codebase and the test suite that keeps it fixed. Can I just ask the AI to fix its own code? Partly. An AI tool will happily add a validation check or wrap a call in error handling once you point at the spot.",
  "articleSection": "Engineering",
  "author": {
    "@id": "https://wavect.io/team/christof-jori/#person",
    "@type": "Person",
    "name": "Christof Jori",
    "sameAs": [
      "https://www.wikidata.org/wiki/Q139796367",
      "https://www.linkedin.com/in/jocr77/",
      "https://github.com/jo-chris"
    ],
    "url": "https://wavect.io/team/christof-jori/"
  },
  "dateModified": "2026-07-07",
  "datePublished": "2026-06-08",
  "description": "AI-generated code from Lovable, Cursor, Claude Code, or Replit demos cleanly and breaks predictably: missing authorization checks, unvalidated input, secrets in client-side code, and no error handling cluster on every build. The single most common serious defect is a working login screen with no server-side check stopping user A from reading user B's data. The post is the QA process Wavect runs on AI-assisted builds before they go live.",
  "headline": "QA for AI-Generated Code",
  "image": "https://wavect.io/img/blog/headers/header_qa-for-ai-generated-code.svg",
  "inLanguage": "en",
  "mainEntityOfPage": {
    "@id": "https://wavect.io/blog/qa-for-ai-generated-code/",
    "@type": "WebPage"
  },
  "publisher": {
    "@id": "https://wavect.io/#organization",
    "@type": [
      "Organization",
      "ProfessionalService",
      "LocalBusiness"
    ]
  },
  "url": "https://wavect.io/blog/qa-for-ai-generated-code/",
  "wordCount": 1422
}
```

```json
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "item": "https://wavect.io/",
      "name": "Home",
      "position": 1
    },
    {
      "@type": "ListItem",
      "item": "https://wavect.io/blog/overview/",
      "name": "Blog overview",
      "position": 2
    },
    {
      "@type": "ListItem",
      "item": "https://wavect.io/blog/topics/delivery-qa/",
      "name": "Delivery and QA",
      "position": 3
    },
    {
      "@type": "ListItem",
      "item": "https://wavect.io/blog/clusters/qa-production/",
      "name": "QA and production readiness",
      "position": 4
    },
    {
      "@type": "ListItem",
      "item": "https://wavect.io/blog/qa-for-ai-generated-code/",
      "name": "QA for AI-Generated Code | ",
      "position": 5
    }
  ]
}
```
