You vibe-coded an app. Here's what breaks when you try to ship it.
What goes wrong when an AI-built prototype meets the App Store, how to triage it in a day, and how to tell a fixable codebase from a rewrite.
A working demo and a shippable app are different things, and the gap between them is where most vibe-coded projects stall. The prototype does everything it did in the screen recording. Then it meets a real user on a bad network with a three-year-old phone, and the failures start arriving faster than anyone can patch them.
We get asked to look at these fairly often now. Someone built a real product without an engineering team, which used to be impossible, and they were right to do it. What they need next is an honest read on what they have.
The problem isn't that AI writes bad code
Current models write reasonable code. What they do not do is hold a whole system in mind across fifty separate prompts. Each request gets answered on its own terms, sensibly enough, and you end up with a codebase where every file looks fine and no two files agree on anything.
The failures cluster in the same places nearly every time:
- API keys and secrets compiled into the app bundle, where anyone can extract them.
- Authorisation enforced in the UI instead of on the server. The screen is hidden; the endpoint is wide open.
- No migration path for stored data, so the first schema change logs existing users out or loses their content.
- The same piece of state kept in three places that drift apart under real use.
- Errors caught and swallowed, so a failed request looks identical to an empty result.
- No offline or failure states at all, because the prototype was only ever demoed on office wifi.
The first two are the ones that cost money. This pattern shows up in almost every AI-assisted build we review:
// Both of these ship inside the app bundle.
const OPENAI_KEY = "sk-proj-..."; // extractable from the IPA in minutes
const ADMIN_EMAILS = ["me@startup.com"];
// A client-side claim. The server never checks it, so anyone
// who calls the endpoint directly is premium, and anyone who
// finds the key is spending your money.
if (user.isPremium) {
await callExpensiveAPI(OPENAI_KEY, prompt);
}
Nobody prompted for this. It is what you get when a model is asked to make a feature work and never told where the trust boundary sits, because nobody knew to mention there was one.
App Review is usually where it surfaces
Plenty of these apps work well enough that the owner only discovers the problems at submission. The rejections repeat:
- Guideline 4.2, minimum functionality. A thin wrapper around a website is not an app, and a lot of AI-built projects are exactly that.
- Missing account deletion, which is required for any app that lets users create an account.
- Digital goods sold through a payment provider that isn't In-App Purchase.
- Permission prompts with no purpose string, or strings that don't describe the actual use.
- Privacy nutrition labels that don't match what the app's own dependencies collect.
None of these are hard to fix. They are just invisible until someone who has shipped before reads the project against the guidelines, and each rejection round costs a week.
What a one-day triage looks at
Before estimating anything, we answer five questions. They sort almost every project correctly:
- Does it build and run from a clean clone, with no machine-specific steps? More often than you'd expect, it doesn't.
- Where does authorisation get enforced, in the UI or on the server?
- What does the app do in airplane mode, and on a request that times out?
- Is there a real data model, or is the shape of the data whatever the API happened to return?
- How many places does a single piece of state live?
Those answers tell you whether you have a codebase with one weak layer you can replace, or one with no seams at all.
Most of it is worth saving
The prototype already proved the idea. That was the expensive part, and it does not need redoing.
Most of the time the answer is repair. The usual shape of that work: put a real boundary between the app and its backend, move every secret and permission check behind it, then give the UI a data layer to talk to instead of calling the network directly. The screens tend to survive intact, which is the part the founder spent the most time on anyway.
When we'll tell you to start over
Sometimes repair costs more than a rebuild, and saying so early is the useful thing we can do. The signals:
- The data model can't represent what the product has become, and every feature is now a workaround stacked on the last one.
- Business rules are spread across UI components with no seam to extract them through, so any change means touching everything.
- The dependency set pins the project to an abandoned framework version that blocks the current SDK.
- The core feature is built on an API whose pricing doesn't survive contact with real usage.
Two or three of those together and a rebuild is the faster route. We would much rather say so in week one than bill three months of patching to arrive at the same place.
How we work on these
A fixed-scope audit first: a few days with the codebase, ending in a written report you keep either way. It covers what is exposed, what will fail review, what the repair involves, and a straight recommendation. Sometimes that recommendation is that you do not need us.
If repair is the answer, that report becomes the plan and the estimate. If it is not, you spent a few days finding out, not a few months.