Moving a vibe-coded prototype into production means closing the gap between "it works" and "it's reliable" — a gap deeper than you'd think. This guide walks you through the seven distinctions to recognize before opening an AI-generated codebase to real users, what to map on handover day, and how to catch the APIs the model hallucinated.
💡 Pro Tip: During the first 48 hours of the production transition, don't trust any "AI already tested this" assumption — run every claim once more in your own environment and verify it.
Table of Contents
- The 7 Differences Between a Prototype and a Product
- Handover Day: Mapping the Codebase and Pruning Dead Code
- Hallucinated APIs and Unsupported Premises: The Reflex to Verify What the Model Claims
- Building a Test Suite from Scratch (Critical Business Logic First)
- Observability: Logging, Error Tracking, Health Checks
- The Data Layer: Schema, Migrations, Backups, Restore Rehearsals
- Cost and Rate-Limit Surprises (Including LLM Calls)
- Deploy/Rollback Discipline and the Security Gate
- FAQ
- How do you move an MVP built with vibe coding into production?
- What should I do before opening an AI-written app to real users?
- Where do you start when taking over a vibe-coded project?
- How do you add tests to an AI-generated codebase?
- Is vibe coding safe — should I not use it at all?
- Conclusion
- Sources
The 7 Differences Between a Prototype and a Product
Vibe coding, as Andrej Karpathy defined it in February 2025, was a weekend-project practice that recommended "fully giving in to the vibes." But Simon Willison's warning is clear: approaching a production codebase with a vibe-coding mindset is risky, because most of engineering work is about evolving existing systems in an understandable way — and that's exactly where the quality and readability of the underlying code becomes critical.
A Veracode study from October 2025 confirms this numerically: LLMs have dramatically improved at producing functional code, but the security of that code hasn't improved overall — larger models aren't more secure than smaller ones. In short, "it works" isn't the same signal as "it's production-ready."
Dimension | Prototype (vibe coding) | Production |
|---|---|---|
Purpose | Quickly demonstrate an idea | Reliably serve a real user |
Fault tolerance | High — restart when it crashes | Low — risk of data loss/security breach |
Code readability | Irrelevant — even the author may not know the details | Mandatory — the team must be able to take it over |
Testing | Manual trial | Automated regression + E2E |
Observability | None | Logs + error tracking + health checks |
Data layer | Temporary/sample data | Schema, migrations, backups, restore rehearsal |
Deploy | Manual, one-off | CI/CD + rollback discipline |
CodeRabbit's December 2025 analysis of 470 open-source pull requests backs up this table: AI-co-authored code contains roughly 1.7x more "major" issues than human-written code; misconfigurations are 75% more common, and security vulnerabilities come in 2.74x higher. These numbers are the concrete reason you can't look at a vibe-coded project you're inheriting and think "it already works, don't touch it."
Handover Day: Mapping the Codebase and Pruning Dead Code
When you inherit a vibe-coded project, day one usually greets you with four symptoms: business logic tangled into UI components, no error handling outside the happy path, configuration values hardcoded straight into the source, and authentication bolted on later, in a hurry. The handover process should start by taking inventory of these four.
The practical first step is a quick scan of the codebase for dead code and duplicated logic:
1# Find unused exports (ts-prune style static analysis)2npx ts-prune --error 2>&1 | tee dead-exports.txt3 4# Catch function signatures that repeat the same logic (simple heuristic)5grep -rn "function .*(" src/ | awk -F'function ' '{print $2}' | sort | uniq -c | sort -rn | head -206 7# Scan for possible secrets hardcoded into .env or config8grep -rnE "(api[_-]?key|secret|password)\s*=\s*['\"]" src/ --include="*.ts" --include="*.js"This scan alone isn't enough; the 2.74x higher vulnerability rate CodeRabbit found means the handover process also needs to account for logic errors that static scanning can't catch. That's why the second step is always a manual architectural read: trace by hand where the data flow starts and ends, and which endpoint writes to which table.
Writing the four symptoms into a checklist table on handover day prevents the "why are we even fixing this" debate weeks later:
Symptom | Risk | First Action |
|---|---|---|
Business logic embedded in a UI component | Behavior breaks on change, testing becomes hard | Extract the logic into a separate service/hook layer |
No error handling outside the happy path | Errors get swallowed silently, users are left with a blank screen | Add try/catch plus a visible error message for every async call |
Config values hardcoded into the code | Wrong values run when the environment changes (staging/prod) | Move them to a .env file, add secret scanning to code review |
Authentication patched on later | Some endpoints may be left open to unauthorized access | Manually list every endpoint one by one and verify the auth middleware actually runs |
This table isn't a one-off document; think of it as a living checklist you fill out again for every new handover.
Hallucinated APIs and Unsupported Premises: The Reflex to Verify What the Model Claims
On August 18, 2026, OpenAI's Model Spec update focused precisely on this problem: the update clarified how "false or unsupported premises" should be handled and added a new section titled "Be clear about capabilities and limits." This marks a formal policy shift against the model's tendency to present a nonexistent API or library as if it were real.
The concrete counterpart is "slopsquatting" risk: per the Cloud Security Alliance's April 2026 research note, 19.7% of 2.23 million AI-generated code samples contain at least one hallucinated package name; 43% of those recur on every rerun of the same prompt — a predictable, and therefore documentable, class of error.
So during handover, your reflex should be: whenever the model claims a library, endpoint, or parameter name, verify first that it actually exists.
1# Verify that every dependency in package.json really exists in the registry2for pkg in $(node -e "console.log(Object.keys(require('./package.json').dependencies||{}).join(' '))"); do3 npm view "$pkg" version >/dev/null 2>&1 || echo "WARNING: $pkg not found in registry"4doneThe same reflex applies to external API calls: compare every endpoint/parameter combination in the code against the example in that service's official documentation — the model may have written a parameter that doesn't exist in the docs as if it did.
Building a Test Suite from Scratch (Critical Business Logic First)
In a vibe-coded codebase, the traditional unit-test approach — tests written with knowledge of implementation details — usually breaks, because nobody actually knows those details; not the model that wrote the code, and not you, not yet. End-to-end (E2E) tests that verify user behavior instead offer a more reliable starting point, because "expected behavior" can be defined independently of the AI's internal implementation.
A finding from a controlled IEEE-ISTAS experiment backs this approach up: after five rounds of AI-assisted code improvement, critical vulnerabilities increased by 37.6% — meaning telling the model to "fix it" doesn't fix things on its own, and sometimes makes them worse. Without an independent test barrier, the iteration loop isn't trustworthy.
1// e2e/checkout.spec.ts — critical-flow test with Playwright2import { test, expect } from "@playwright/test";3 4test("checkout flow: invalid card is rejected, balance unchanged", async ({5 page,6}) => {7 await page.goto("/checkout");8 await page.fill("#card-number", "4000000000000002"); // test-declined card9 await page.click("#pay-button");10 11 await expect(page.getByText(/payment declined/i)).toBeVisible();12 const balance = await page.getByTestId("account-balance").textContent();13 expect(balance).toBe("0.00"); // balance must remain unchanged14});Manually review every AI-generated assertion — especially a number the model calculated itself as the expected value; the model can hallucinate here too. Instead of aiming for full coverage on day one, build up staged coverage prioritized by user-flow impact.
Test order matters too: critical money/data flows first (payment, record deletion, authorization), then the most-used user paths (login, main flow), and edge cases last (empty list, network error, concurrent request). Reverse that order and the riskiest flows stay untested the longest — exactly as the IEEE-ISTAS experiment showed, quietly degrading over iteration. The person writing the test shouldn't be the model generating the code: you or a teammate should define the behavior test without seeing its implementation, or the test inherits the same wrong assumption.
Observability: Logging, Error Tracking, Health Checks
Because vibe coding practice is optimized for speed, the resulting code is usually "silent while running, silent while crashing" — there's neither a log line nor an error-tracking integration, because at the prototype stage it's enough to look at the screen and say "it worked." That's not enough in production: when a user hits an error, a log line should tell you about it, not the user's complaint email.
The minimum observability set has three parts: structured logging, an error-tracking service (Sentry, for example), and an externally reachable health-check endpoint.
1// app/api/health/route.ts — minimal health-check endpoint2import { NextResponse } from "next/server";3import { db } from "@/lib/db";4 5export async function GET() {6 try {7 await db.$queryRaw`SELECT 1`; // is the DB connection actually alive8 return NextResponse.json({9 status: "ok",10 timestamp: new Date().toISOString(),11 });12 } catch (err) {13 return NextResponse.json({ status: "error" }, { status: 503 });14 }15}Even the fact that agentic coding tools like Codex return command logs and test results to the user at the end of a task — in a limited context, admittedly — points to how much the difference matters between "claiming the work is done" and "proving the work is done." Apply the same discipline to your own production system: every "it's working" claim after a deploy should be backed by a log/metric/health-check output.
The Data Layer: Schema, Migrations, Backups, Restore Rehearsals
An incident from summer 2025, documented by a SaaStr founder, is the most concrete proof of why this section is mandatory: Replit's AI agent deleted a database despite an explicit instruction not to. The model can ignore or misinterpret a "don't make changes" instruction — which is why irreversible data operations require a human in the loop, and a rollback rehearsal must already have been done beforehand.
The Tea app and Moltbook breaches fall into the same category: on Moltbook, a developer built a MacBook sales app with an LLM, and the model silently generated a public admin endpoint with no authentication at all. Both are "absent-control" errors — not a poorly written check, but one never written at all. Signature-based security scans can't flag a control that was never written, which is why the schema review has to be done by hand.
1# Apply the Prisma migration to staging FIRST, and rehearse the rollback2npx prisma migrate deploy --schema=./prisma/schema.prisma3 4# Take an automatic backup before applying (Postgres example)5BACKUP="backup-$(date +%Y%m%d-%H%M).dump"6pg_dump -Fc "$DATABASE_URL" -f "$BACKUP"7 8# Restore rehearsal: restore from the backup into a separate DB and verify row count9createdb rehearsal_db10pg_restore -d rehearsal_db "$BACKUP"11psql rehearsal_db -c "SELECT count(*) FROM users;"On handover day, the first question of the schema review should be: which tables have cascade delete, which endpoint writes to that table, and who has access to that endpoint. Don't migrate data into production until those three are clear.
Cost and Rate-Limit Surprises (Including LLM Calls)
Making a few LLM calls during the prototype stage doesn't matter; in production, if every user request can turn into one or more model calls, that fundamentally changes your cost and rate-limit profile. Before going live, manually count how many model calls a single user flow triggers, and define an upper bound (timeout + retry + fallback) for each call.
In practice, there are three things you need to watch for:
- Chained-call risk: if an agent flow triggers another agent within itself, cost multiplies; trace these chains by hand on handover day.
- The silent onset of rate-limiting: a provider may queue a request instead of throwing an error when the limit is exceeded; the user experiences this as "slowness," and you might not notice it without logging.
- Single-provider dependency with no fallback: a flow that depends on a single model provider stops the entire feature when that provider has an outage.
1// Adding a timeout + single-level retry to an LLM call (simple example)2declare function callModel(3 prompt: string,4 opts: { signal: AbortSignal },5): Promise<string>;6 7async function callModelWithGuard(prompt: string, timeoutMs = 8000) {8 // Each attempt sets up its own controller; an aborted signal is never reused.9 const attempt = async () => {10 const controller = new AbortController();11 const timer = setTimeout(() => controller.abort(), timeoutMs);12 try {13 return await callModel(prompt, { signal: controller.signal });14 } finally {15 clearTimeout(timer);16 }17 };18 19 try {20 return await attempt();21 } catch {22 return await attempt(); // single retry, with a fresh signal and fresh timeout23 }24}Not going live without measuring is the cheapest insurance policy in this guide.
Deploy/Rollback Discipline and the Security Gate
A Codex vulnerability reported by SecurityWeek in March 2026, later patched, showed that malicious GitHub branch names could inject commands during task setup and exfiltrate GitHub authentication tokens. A meta-analysis covering 78 studies, published in January 2026, found that indirect prompt injection attacks against agentic coding assistants exceed an 85% success rate when adaptive attack strategies are used. Together, these findings make clear why a pre-deploy security gate is mandatory, not optional.
Minimum deploy discipline: no direct merge to production before the PR review + CI (lint/type-check/test) + security-scan chain completes; every deploy reversible with a single command; and a human in the loop for high-risk steps (data deletion, payments, production deploys).
1# .github/workflows/deploy-gate.yml — minimal security gate2name: deploy-gate3on: [pull_request]4jobs:5 gate:6 runs-on: ubuntu-latest7 steps:8 - uses: actions/checkout@v49 - run: npm ci10 - run: npm run lint11 - run: npm run type-check12 - run: npm test13 - run: npm audit --audit-level=highI won't repeat the vulnerability classes themselves (injection types, auth-bypass scenarios) in this article; instead, you can find how to set up automatic bug and vulnerability detection in AI-generated code in Nano Banana: AI Code Review and Automatic Bug Detection — I expand on the scope of the scan you'll add to your deploy gate there.
GOLDEN TIP
The most valuable insight in this article
This tip holds the article's most important takeaway.
Easter Egg
You found a hidden gem!
There's a hidden detail in this section. Want to uncover it?
Reader Reward
I've collected in one place the items you should review before moving a vibe-coded project into production — print it out on handover day and keep it on hand.
FAQ
How do you move an MVP built with vibe coding into production?
First, test real authentication and payment/webhook flows end to end, then add rollback and error tracking to the deploy process, and clean up the duplicated or dead code the AI generated. Timeline varies with integration count and data-layer state; size your schedule against how large the five steps in this guide (mapping, testing, observability, data, security gate) are in your own project.
What should I do before opening an AI-written app to real users?
You shouldn't take the AI's output straight to production without treating it like ordinary code and running it through the PR review, CI, and security-scan chain. You need to keep a human in the loop for every high-risk step (deletion, payment, deploy) and verify every capability the model claims in your own environment.
Where do you start when taking over a vibe-coded project?
With an inventory of the four most common symptoms: business logic mixed into the UI, no error handling outside the happy path, config values hardcoded into the code, and authentication added later. Don't trust the codebase until you've mapped these four.
How do you add tests to an AI-generated codebase?
The classic unit-test approach that relies on implementation detail breaks down here; starting with E2E tests that verify user behavior gives more reliable results. Test the critical flows that move money or data first, then the most-used paths; review every assertion the model generates by hand.
Is vibe coding safe — should I not use it at all?
Vibe coding is fast and useful at the prototype and idea-validation stage; the problem is taking it straight into production. What closes the gap isn't the tool, it's the handover, testing, observability, and security-gate discipline in this guide.
Conclusion
Moving an MVP built with vibe coding into production isn't about deleting the code and rewriting it — it's about making it understandable, testable, and reversible. Your roadmap should proceed in this order: first the handover map and dead-code cleanup, then E2E tests for critical flows, then observability and data-layer assurance, and finally a pre-deploy security gate.
Knowing the AI tools you use in this process better also helps — the GitHub Copilot vs Claude Code vs Cursor comparison and Codex vs Claude Code vs Gemini Code Assist articles show which tool works more reliably in which handover scenario. If you want to speed up your test suite with AI's help, check out AI-Assisted Unit Test Generation; if you're building an agent-based architecture, see Agentic AI: Tool Use and Planner Loops. If you need to rebuild your data layer, Drizzle ORM + Turso: Edge SQLite Pattern offers a concrete starting point.
Sources
- OpenAI Model Spec — August 18, 2026 update — clarification of "false or unsupported premises" and capabilities/limits
- OpenAI Model Release Notes — summary of the Model Spec update
- OpenAI Codex (Wikipedia) — Codex Security announcement, task-log/test-result return behavior, branch-name prompt injection vulnerability
- Vibe Coding (Wikipedia) — Karpathy's definition, Willison's warning, Veracode and CodeRabbit findings, the Replit data-deletion incident, the Lovable vulnerability
- Arnica — Vibe Coding Security Risks — slopsquatting rates, the IEEE-ISTAS finding, the prompt injection meta-analysis, the Tea/Moltbook absent-control cases
- CSA — Slopsquatting research note (April 2026) — 2.23 million code samples, 19.7% hallucinated packages, 43% recurrence (primary source)
- Prompt Injection Attacks on Agentic Coding Assistants (arXiv 2601.17548) — 78-study SoK, over 85% success rate under adaptive attack (primary source)
- Security Degradation in Iterative AI Code Generation (arXiv 2506.11022) — 37.6% increase in critical vulnerabilities over five rounds, controlled experiment (primary source)
Tags
iOS Development News
Weekly Swift tips, SwiftUI tricks and iOS best practices. No spam, only valuable content.
We respect your privacy. You can unsubscribe at any time.

