Shipped by AI, Stuck at Feature Five: What Actually Breaks in Vibe-Coded Codebases
The app works. The demo is clean. You went from a whiteboard sketch to a deployed product in weeks instead of months, and most of the code was generated.
Then comes feature five.
It's a small request. A discount code field. A second user role. Something that should take two days. It takes three weeks, and when it finally ships, checkout breaks for a subset of users. Nothing went wrong during those three weeks. The failure was priced into the codebase in week one, and feature five is just when the invoice arrived.
I audit codebases in this exact state fairly often now, and the pattern is consistent enough to be predictable. What follows is what's actually happening under the hood — and, more usefully, how to tell whether it's happening to you without needing an engineer to explain it.
The mechanism: local correctness, global incoherence
To understand why these codebases stall, it helps to be precise about what a language model optimizes for. It's making this change work, right now, given what's in its context window. It is not keeping your system coherent over time, because coherence isn't visible from inside a single prompt.
Every prompt is a fresh context. The model doesn't remember the architectural decision it made last Tuesday unless you put that decision back in front of it. It doesn't know you already have a unified way to handle API errors, so when a ticket needs error handling, it writes a new one. Both work. Neither is wrong.
That's local correctness with global incoherence. Any individual function reviews well in isolation. Zoom out and the system is several contradictory systems wearing a trench coat. Everything below is a symptom of this one thing.
Where this is actually the right trade
Before the diagnosis, the concession — because the opposite mistake is real and I see it too.
Generating the first version of a product fast is usually correct. Most software should not exist, and the cheapest way to find out whether yours should is to build a working version and put it in front of people. Two years ago that cost you three months and a contractor. Now it costs a weekend. That is a genuine improvement, and founders who refuse it out of craft anxiety lose to founders who don't.
The failure isn't in generating the code. It's in the handoff that never happens — the moment where a disposable prototype quietly becomes the thing you're going to run a business on, and nobody marks the transition. Generated code is cheap to produce and expensive to inherit, and the inheritance happens silently, usually around the time you get your first paying customers.
So the question is never "should we have used AI." It's "did anyone decide this was permanent, and did anything change when they did."
The five things that actually break
When I open a repo that's hit this wall, I rarely find one catastrophic bug. I find the same five structural problems in different proportions.
What actually breaks
Not one bug. Five structural problems.
Features 1-4 were greenfield. Five has to integrate.
Cost to ship
- 01
Duplicated logic
Same rule, many homes
- 02
Invented abstractions
Nothing composes
- 03
Decorative tests
Green, not useful
- 04
Schema drift
Data is not disposable
- 05
Unchosen dependencies
Nobody chose them
1. Business logic duplicated across the codebase
The model was asked to calculate a cart total on the checkout page, so it wrote that logic there. Later it was asked to show the total in the confirmation email, and the email template wasn't in context, so it wrote the calculation again. Then again in the admin order view. Then again in the refund flow.
You now have the same rule in five or six places. Each one is correct in isolation. They disagree about edge cases — one rounds before applying tax, another after; one handles a null discount, another throws.
The cost isn't the duplication itself. It's what duplication does to the arithmetic of every future change. Adding a promotional discount stops being one edit and becomes: find every place the total is computed, verify each one, change each one, and confirm you didn't miss one — with no reliable way to confirm that last part. A two-day feature becomes three weeks not because the work is hard, but because the search is unbounded.
This is the same trap I wrote about in The MVP Trap, arriving through a new door. The mechanism there was time pressure. Here it's context windows. The bill is identical.
2. Abstractions invented per prompt
Because the model can't see the whole system, nothing composes. Rather than extending an existing UserService, it invents a UserAccountManager for one feature and a ProfileDataHandler for another. Three months in, you have four ways to fetch a user and no way to know which one is canonical.
The practical effect is that every new feature costs more than the last one, in a codebase where the first four features cost almost nothing. That curve is the actual signature of this problem, and it's the reason feature five is where teams notice. Features one through four were all greenfield. Five is the first one that has to integrate.
3. Tests that assert the implementation, not the behavior
Ask a model to write tests for code it just wrote and you'll get tests shaped like the code. The most common form looks roughly like this: the test mocks calculateTotal() to return 100, calls the checkout function, and asserts the total is 100.
That test passes whether or not calculateTotal works. It will pass after you break it. It's not testing your pricing — it's testing that your mock returns what you configured it to return.
A suite full of these is worse than no suite, because no suite is honest about the risk. Green tests tell your team that changes are safe, and the team believes it, right up until a regression reaches customers and everyone discovers the coverage number was decorative. If you want one number to distrust, it's test coverage on a generated codebase.
4. Schema drift — where the bill actually lands
Code is disposable. If a module is bad, you can regenerate it in an afternoon. Data is not disposable, and this is the difference founders consistently underestimate.
Models will happily satisfy a prompt by adding a nullable column instead of a proper relation, or storing a status as free text so that production ends up holding active, Active, ACTIVE, and enabled in the same column. User data ends up split across two tables that each half-own it. Each of those decisions was the fastest way to close a ticket. None of them was a decision anyone made.
Then you need to fix it, and the shape of the work is nothing like fixing code:
- You write the new schema, which is the easy part and the only part that feels like progress.
- You write a backfill that converts every existing row, including the ones that don't fit — the twelve accounts from March where the status is empty, the orders with a currency field that's null because that column didn't exist yet.
- You run it against production data, where it will behave differently than it did against your seed data, because production data is always weirder.
- You keep the old and new paths alive simultaneously while it runs, which means writing code for both models and deleting half of it later.
- You do all of it without downtime, because you have paying customers now.
That's the expensive part of the invoice, and it lands as engineer-weeks with nothing shippable at the end.
There's a quieter cost too. Long before the migration, you lose the ability to answer basic questions about your own business. "How many active subscribers do we have" becomes a research project, because two systems disagree about what active means and neither is obviously wrong. Founders usually feel this before they feel the technical version.
5. Dependencies nobody chose
A package gets pulled in because it fit the shape of a prompt. Nobody evaluated it, nobody compared it to alternatives, nobody checked whether it's maintained.
Two distinct risks live here, and they get conflated. The first is hallucinated packages — the model invents a name that doesn't exist. These are loud and mostly harmless; the install fails and you notice immediately. The second is the real one: attackers watch which names models invent, register those names, and publish something malicious under them. The install then succeeds. Nothing fails. You have a dependency that arrived because a model guessed at a name and someone was waiting.
While auditing a client repository recently, I found a live malware payload in the dependency tree. What made it worth writing about wasn't the payload — it was that nobody on the team could tell me when it arrived or who had approved it. There was no answer to that question because the question had never been asked.
That's the general case. This is also, incidentally, a build vs. buy decision being made dozens of times by nobody in particular. Every dependency is a decision to rent rather than build, which is often correct — but renting from a landlord you never checked is a different thing entirely.
The reframe: AI removed the rate limiter
AI didn't invent technical debt. We were producing it at industrial scale long before any of this.
What changed is that human typing speed used to function as an accidental governor on scope. Because features took real time to write and wire up, there were forced pauses — moments where an engineer, halfway through the tedious part, thought wait, why are we doing it this way, and changed course. That reflection wasn't a process anyone designed. It was a side effect of friction.
The friction is gone. You can now produce ten thousand lines of structural debt in an afternoon, and nothing in the workflow makes you stop and look at it. The rate at which you can dig has increased enormously. The rate at which you can climb out has not moved at all.
Nobody replaced the governor. That's the whole problem, and it's a process problem rather than a technology one — which is good news, because process problems are cheap to fix once you've named them.
Five questions a founder can ask this week
You don't need to read code to diagnose this. Ask your team these, and listen to how quickly the answers come.
"Where is the price calculated?" If the answer is more than one place, you have duplicated business logic. If the answer is "let me check," you have it and don't know the extent yet.
"If we add a new user role next month, what has to change?" You're not looking for a number. You're looking for whether someone can name the list from memory. If the answer is a shrug or a search, your abstractions aren't holding.
"If this afternoon's deploy is wrong, can we roll the database back?" If the room goes quiet, your schema is drifting. This is the question I'd ask first if I could only ask one.
"Which test would have caught the last bug that reached a customer?" If the answer is none, the suite is decorative regardless of what the coverage badge says.
"Who approved the last dependency we added?" If there's no name, nobody is watching that door.
None of these require technical knowledge to ask, and the hesitation before the answer tells you as much as the answer does.
What to do instead of a rewrite
The instinct at this point is to burn it down and start clean. Resist it — I've written at length about when a rewrite is actually justified, and this situation is almost never it. You'd be regenerating the same class of code with the same tooling, arriving at the same place eight weeks later with less runway.
What actually works is narrower:
Consolidate the business rules first. Pick the two or three rules that decide money — pricing, entitlement, refunds — and give each exactly one home. Not the whole codebase. Just those. This is usually a week of work and it removes most of the unbounded-search problem.
Take ownership of the schema. From today, migrations are written by a person and reviewed by a person. No generated migration reaches production unreviewed. This costs almost nothing going forward and is the single highest-leverage rule on this list, because it stops the expensive category of damage from accumulating further.
Put one human gate on the dangerous surface. Money, authentication, permissions, personal data. Everything else can move at generated speed. This isn't a code review culture — it's four categories and one reviewer.
Write down decisions as you make them. The reason nobody knows why anything is the way it is, is that the author was a model and can't be asked. A short decision log costs minutes and is the difference between onboarding an engineer in days versus weeks.
Notice that none of this is "use less AI." It's putting back the governor that friction used to provide, deliberately, in the four places where being wrong is expensive.
The value shift
The bottleneck in building software has moved. It is no longer producing code, and it hasn't been for a while now. It's deciding what deserves to exist and how it connects to everything already there.
Having a human in the loop was never about syntax. It's about coherence — and coherence is exactly the thing that can't be generated one prompt at a time.
If feature five already feels expensive
If the demo shipped fast and every change since has gotten slower, the codebase does not need a rewrite. It needs someone to mark what is permanent and put a governor on the expensive parts.
That is the kind of work I do with founders who inherited a generated product and now have paying customers on it.
Related posts

The MVP Trap: How Quick and Dirty Code Makes Every Feature Expensive
April 20269 min readQuick MVP code feels cheap until every feature ships slower—learn how founders avoid paying twice for the same product.

Rewrite or Refactor? The Decision That Burns Founder Money
May 202610 min readBefore rebuilding your app from scratch, learn when a rewrite is worth it, when refactoring wins, and the hidden costs founders miss.

Build vs. Buy: The Hidden Cost of Writing Code You Could Have Rented
June 202611 min readFounders often burn months of runway building commodity features from scratch to save on SaaS subscriptions. Here's how to make better build vs. buy decisions and ship faster.
Questions about something you read here, or a project you want to move forward? I work with teams on full-stack builds, AWS and serverless consolidation, migrations, and messy systems.
Contact me