The code nobody read
The product was real. Paying customers, a proper login, a billing integration, a dashboard, an API, a mobile-friendly front end. Two founders had built it in four months with a coding agent doing most of the typing, and it was better than most things I have seen come out of a four month build. Then one founder left, the other needed to raise money and stop coding, and I was asked to take it over.
I said yes, and the first thing I did was count. Sixty-one thousand lines of TypeScript, not counting tests, in a repository that was 140 days old. For comparison, the largest product I had built myself with a team of four over a year was around 45,000. This had been produced at roughly ten times the rate, and the rate is not the problem. The problem is what the rate implies about how much of it a person had read.
I asked the remaining founder. His honest estimate was that he had read, carefully, maybe a fifth of it. The rest he had reviewed the way you review a demo: does the feature work, does the screen look right, ship.
The first bug
The first bug report after I took over was that some invoices had the wrong tax rate. Not all. Some. The kind of bug that used to take an afternoon on a codebase I knew: find where tax is computed, find the branch that picks the rate, find the condition that is wrong.
It took a week. Not because the code was bad, exactly. Because tax was computed in four places. There was a computeTax function in a billing module, which was the one I found first and which was correct. There was a second one inside the invoice PDF generator, which had been written on a different day for a different feature and which had its own rate table, three months out of date. There was a third inline in the checkout flow that called the first one and then adjusted the result for a discount case. And there was a fourth in a Stripe webhook handler that recomputed the tax from the line items to validate the incoming amount, using rules that were subtly different from all three others.
Each was reasonable in isolation. Each had been written by an agent asked to implement a feature, which had looked at the immediate surroundings, not found a tax function in scope, and written one. Nobody had read enough of the codebase to know there were already three. The bug was that customers who got the PDF saw one number and customers who got the email saw another, and which one was "wrong" depended on which of the four you considered canonical.
That is the shape of every bug I found in the first two months. Not wrong logic. Duplicated logic that had drifted.
What vibe coding actually produces
I want to be careful here, because the easy version of this post is "AI code is bad" and that is not what I found. Line by line the code was fine. Better named than a lot of human code, consistently formatted, with reasonable error handling and tests that passed. If you sampled any 200 lines you would think the team was solid.
What the process produced was a codebase without a shape. Human teams, even bad ones, develop a shared picture of where things live, because they have to read each other's code to work on it. That picture is what stops the fourth tax function from being written: someone on the team would say, we have one of those, use it. When the agent does the writing and the humans check the output, nobody has to read anything, so the picture never forms, and every feature is built as if it were the first.
The symptoms, concretely, in this repository:
Four tax implementations, three date formatting helpers, two permission checks with different rules, and a utils directory with 90 files, eleven of which were named some variant of format.
Twelve database access patterns. Some Prisma, some raw SQL, some through a repository class that existed for four tables and not the other thirty.
Environment variables read in 60 places, with three different fallback conventions.
A test suite of 2,100 tests with a 91 percent pass rate, where the failing 9 percent had been failing for weeks and were being ignored because the features they tested had been reworked without the tests being deleted.
None of these are AI problems. All of them are what you get when code is produced faster than it is read, and the agent just made the first half of that possible at a scale humans could not reach before.
The week I spent not fixing anything
After the tax bug I stopped taking tickets for a week and did what the founders never had time for. I read it. Not all 61,000 lines, but every module boundary, every place that touched money, auth, or the database, and every file over 300 lines. I made a map: what lives where, which of the duplicates is canonical, which ones are dead.
Then I wrote the map down, in the repository, as the kind of file the agent reads at the start of every session. Tax is computed in billing/tax.ts and nowhere else. Dates are formatted with lib/format/date.ts. Database access goes through the repository layer, and here is how to add a table to it. Here are the modules that are considered legacy and must not be extended.
That file changed the agent's behaviour more than any prompt tuning could have. The next feature it built used the canonical tax function, because the file told it one existed and where. The founders had been giving the agent a fresh, empty picture of the codebase every session, and it had done what a new contractor does with an empty picture: built what it needed in front of it.
Deleting
The second month was mostly deletion. About 14,000 lines went, which is nearly a quarter of the codebase, and no feature was lost. The duplicates were folded into the canonical version, one at a time, each with a test that asserted the behaviour of the version customers had been seeing most. The failing tests were deleted or fixed. The utils directory went from 90 files to 22.
The agent did most of the typing for this too. The difference was that every task started with the map, the task was scoped to one duplicate, and I read every diff, because that was the whole point. Reading was the job. The agent's speed was still useful, it just could not be the only thing.
What I would have done in the four months
The founders did not do anything wrong by the standards of what they were trying to do, which was to get a product in front of customers before the money ran out. They succeeded. But if I had been in the room, the changes I would have pushed for are small and none of them slow the agent down much.
Keep the map from day one. A file that says where things live, updated when something new is added. It costs a minute per feature and it is the single thing that stops the duplication, because it gives the agent the picture a team would have had.
Read one thing per feature. Not the whole diff, that is not realistic at that pace. The one function that touches money, or auth, or the database. Reading the tax function when the second one was written would have caught it that day.
Delete tests that fail for more than a day. A test suite that is 91 percent green is a suite nobody trusts, and a suite nobody trusts is not a test suite. Either the test is wrong and it goes, or the code is wrong and it is fixed, and "we'll look at it later" is the option that produces 9 percent.
Search before writing. Tell the agent, in the map file, to search for an existing implementation before writing a helper. The agents do this if told. They do not do it by default, because by default they optimise for finishing the task in front of them.
Budget the reading. If the agent produces 500 lines an hour and a person can read 150 with attention, the team has a reading deficit of 350 lines an hour that compounds. Either slow the production or accept that the deficit is a debt with a rate, and plan to pay it, which is what I was hired to do.
A number to watch
If there is one metric to put on a wall for a team working this way, it is the ratio of lines merged to lines a human has read with attention, per week. Nobody measures the denominator precisely and it does not matter. A rough self report is enough. When the ratio drifts past three or four to one, the map is going stale, the duplicates are being born, and the debt is compounding at a rate that will be paid by whoever inherits the code.
The founders' ratio, in hindsight, was somewhere around ten to one. Mine on the same codebase now is about two to one, and features ship at the same pace. The difference is that the agent is spending its speed on the right things because someone has read enough to know what those are.
Where it ended up
Six months later the codebase is 52,000 lines with a map, one implementation of everything that matters, and a test suite that is green or the build fails. The agent still writes most of the code. The founder reads the diffs for anything under billing and auth and I read the rest. Features ship about as fast as they did in the first four months, which surprised me, and I think the reason is that the agent spends less time working around the mess it used to make.
The thing I keep coming back to is that none of this was new. A team that hired ten contractors and never read their code would have ended up in the same place in 2015. What is new is that one person and an agent can now produce the output of ten contractors, so the reading deficit that used to take a big team to accumulate can be accumulated by a founder in a spare bedroom, in four months, without ever noticing it is happening. The tool did not create the debt. It removed the friction that used to keep it small.