# Why AI-generated apps fall apart in week three

> It is never the first prompt that goes wrong. The decay has a mechanism, and once you can name it you can design around it.

**Source:** https://graphlit.co/blog/why-ai-generated-apps-rot

**Published:** 2026-08-09

The pattern is consistent enough to set your watch by. Week one is euphoric. Week two is productive. Somewhere in week three you make a small change and something unrelated stops working, and when you go looking for why, you find code you do not recognise doing a job you thought was handled somewhere else.

This is not bad luck and it is not a bad model. It is a mechanism, and it works the same way every time.

## The mechanism

Every AI change to a codebase follows the same three steps: **read the code, infer the intent, write the change.** Step two is the whole problem.

Intent is not in the code. The code records *what* was decided, never *why*. When a model reads a repository it sees the what, reconstructs a plausible why, and edits on the basis of that reconstruction.

| What the code shows | What was actually meant | What the model may do |
| --- | --- | --- |
| Two functions that validate an email | One is for public signup and deliberately stricter | Merge them, quietly loosening public signup |
| A retry loop around one API call | That upstream service is known to be flaky | Remove it as redundant complexity |
| A table with duplicated columns | Denormalised on purpose, for read performance | Normalise it, and slow the main query down |
| Frontend calls the API, never the database | A deliberate boundary: auditing lives in the API | Add a direct database call, because it is shorter |

*Every row compiles. Every row passes the existing tests.*

Note what these have in common. None of them is a crash. Each is a *reasonable* change given only the code, and wrong given the reasoning nobody wrote down.

## The four symptoms, in the order they appear

1. **Duplication**: The same job done in two places, because the model did not find the first one. Cheap on its own. Expensive because now a fix has to be applied twice and nobody knows that.
2. **Boundary erosion**: A layer that was supposed to be crossed one way starts being crossed several ways. Usually invisible until something that depended on the boundary (an audit log, a permission check, a cache) turns out to have a hole.
3. **Orphaned code**: Functions and files nothing calls any more, left behind by changes that routed around them. They make every future search noisier and every future change slower.
4. **Fear**: The terminal state. Nobody is confident what a change will do, so changes get bigger and rarer and riskier, and the project stops moving.

> **Why tests do not catch this**
>
> Tests check behaviour someone thought to write a test for. Every symptom above preserves behaviour, which is precisely why they are hard to spot. A duplicated validator returns the right answer. An eroded boundary still serves the page. The system is wrong structurally while behaving correctly, and no assertion is watching structure.

## Why it accelerates

Decay is not linear, for a reason worth understanding: **each generation reads the previous one's output as if it were intentional.**

Change four introduces a duplicate validator. Change nine reads a codebase where validation happening in two places is now the observed convention, so it adds a third. Change fifteen sees a codebase with no consistent validation strategy and picks arbitrarily.

> Yesterday's accident is today's precedent.

The model is not degrading. It is faithfully following a convention that was never a decision.

## What does not fix it

- **A better model.** It reads the same absent information more thoroughly. The missing why is still missing.
- **A longer prompt.** It works for one change. The next session starts from the code again. See [the thousand-line prompt problem](https://graphlit.co/blog/thousand-line-prompt).
- **More tests.** Genuinely valuable, and aimed at a different target. Tests pin behaviour; this is structure.
- **A diagram in a document.** Correct on the day it is drawn. Nothing connects it to the code, so it silently stops being true and is then worse than nothing, because people trust it.

## What does

The intent has to exist somewhere outside the code, in a form that can be **mechanically compared against** the code. Those two properties together, not either alone.

1. **A structural description of the system**: the parts, and which parts may talk to which.
2. **Rules that are checkable**, not prose: "the frontend must not call payments directly" as something that fails a build.
3. **A comparison that runs on every change**, so divergence is reported the day it happens rather than discovered in week twelve.

That is what Graphlit is. The drawing is the intent. It is typed, so it can be checked. The code is re-read after every change and compared back to it, and anything that no longer matches is reported.

*[Interactive demo: Change the code, and the map stops agreeing. Drift becomes a report, not a surprise.]*

> **The honest limit**
>
> This catches structural divergence: code that no longer matches the design. It cannot tell you the design was right in the first place, and a feature can still be wrong while matching its drawing perfectly. Verification narrows the space of possible mistakes. It does not empty it.

## Frequently asked

### Why does AI-generated code get worse over time?

Because each change reads the previous output as if it were deliberate. An accidental pattern introduced early becomes the observed convention, and later changes reproduce it. The model is not degrading. It is faithfully copying something that was never a decision.

### Can more tests prevent AI code from decaying?

They help, but they target the wrong layer. The common failure modes (duplicated logic, crossed architectural boundaries, orphaned code) all preserve behaviour, so behavioural tests pass. What is needed is a structural check about which parts may call which.

### How do I know if my codebase is already drifting?

Three practical signals: you find two pieces of code doing the same job, you cannot confidently say which parts call which, or you avoid changing certain files. Any one of them means the map in your head has stopped matching the code.

### Is this a problem with a specific AI tool?

No. It follows from generating code out of prompts against a codebase whose reasoning was never recorded, so it appears across every tool in the category. Tools differ in how quickly you reach it, not in whether you do.

