Chapter 04
The Five Failure Modes
The aim here is diagnosis. I want to name five patterns I have seen across AI-assisted teams, clearly enough that you can spot them in your own work, because naming a problem well is most of what makes it fixable. None is universally fatal, and few teams will have all five.
Progress Mirage
Velocity has always been an indirect measure. What a team cares about is how fast useful change reaches users, and the things we count (code produced, PRs merged, story points closed) are stand-ins for that. They stood in well for a long time, because writing the code was usually the slow step, and the deliberation and the several people around it kept the count roughly honest. Faster code meant faster delivery.
AI broke that correlation. Writing code stopped being the slow step. The slow steps are the ones that were always slow: working out what to build, checking that a change solved the problem, seeing how it rippled through the rest of the system, and watching what users did with it. None of those got faster. The part we measure did.
When the measure moves faster than the thing it stands for, it starts to mislead. A team can write code twice as fast, ship twice as often, and deliver the same number of useful changes in a quarter, or fewer. From the inside it is hard to see. The dashboards look fine, the commit graph looks fine, and the retros are about how to keep the pace up.
I see it most often as a slow fatigue. Engineers are busier than they have ever been, shipping more than they ever have, and cannot quite say what the product has done lately. They are not wrong to feel it. The work is real, and it no longer moves the product the way that much effort used to.
The Progress Mirage is mistaking code throughput for progress. It is hard to step out of, because generating code is engaging in a way that resists interruption: there is a strong sense of motion, and stopping to ask whether the motion is forward is a cost few teams pay. The way out is to measure something that has to track the four qualities, not something next to them. What that something is depends on the team, but the test is whether it moves with usefulness, maintainability, debuggability, and evolvability. The time it takes a new developer, or a fresh agent, to clone the repo and get running is a direct reading on maintainability, and nothing about faster code generation improves it.
Change Spiral
Underneath this one is the oldest story in maintenance: code gets more expensive to change as it grows in size, age, and complexity. The cost is not new. What is new is how fast it arrives, and what it feels like when it does.
Code written fast is no harder to change than code written slowly, as long as the understanding behind it survives: the sense of why it is shaped this way, what was considered, what it works around. Generate code in bulk and that understanding does not arrive with it. What lands in the repo is the code without the model of it, and a change to code nobody understands has an unpredictable blast radius. The person making the change cannot see what it will touch.
That unpredictability shows up in two opposite postures. One is the freeze. An engineer who knows enough to be wary will not touch the module, routes around it instead, and over time the codebase grows regions that are read-only in practice. You can technically change them; no one will, and the options they held quietly disappear. The other is the spiral, and in an AI-heavy codebase it is the common one. Someone changes the thing, a second thing breaks, they fix that, a third breaks, and they are playing whack-a-mole inside a system they thought they understood an hour ago. The freeze and the spiral look like opposites, one refusing to touch the code and the other unable to stop, but they are the same failure wearing two faces: no one can predict what a change will do.
The cost is not mainly the bugs you fix. It is the changes you could not safely make, plus the cascades you set off when you tried anyway. None of it lands on a dashboard. It surfaces, eventually, as the sense that the product has become hard to move.
Missing Reasons
You can see the decision in the code. You cannot tell why it was made.
A codebase holds two things: the code, which is what runs, and the reasons behind it: why it is shaped this way, what was considered and rejected, what it honors, what it trades off. The first is always recoverable, because it is what executes. The second survives only if someone wrote it down or still remembers it. This gap is old; institutional knowledge has leaked out of teams for as long as there have been teams.
AI widens the gap by changing the ratio. Code volume goes up, often sharply. The reasoning captured next to it does not rise to match, and under deadline pressure it falls, because generating the code is fast and explaining it is not. The explanation is the first thing dropped.
What you are left with is code whose decisions you can see but cannot account for. retry_count = 3 is right there in the file; why three and not five, what was tried, what failed at five, is nowhere. The engineer who comes to change it reads the conclusion without the argument, guesses at the intent, and writes a follow-on change that fits the guess. The next engineer guesses differently. Over time the codebase holds several incompatible ideas of what it is doing, none of them the original.
This is not a shortage of comments. A file can be heavily commented and still be missing its reasons, the moment the comments say what the code does instead of why it was chosen. The thinking that mattered lived in conversations, reviews, and people’s heads, and when it never reaches the disk, it leaves when they do.
| The code shows | The code does not show |
|---|---|
retry_count = 3 |
Why 3 and not 5, what was tried, what failed at 5 |
if user.region == "EU": … |
The legal review that landed this branch |
# TODO: revisit |
What needs revisiting, by whom, by when |
cache_ttl = 47 |
The incident that pushed it down from 300 |
Invisible Decisions
This is the mode I think is genuinely new, and it differs from the last one in a way worth pinning down. Missing Reasons is about decisions you can see, with their reasons gone. Invisible Decisions is about decisions you never saw at all, because they never looked like decisions.
Every piece of code embeds choices. A few are explicit, like one library over another. Most are implicit and far more numerous: where to validate, whether to retry, what to log, which edge cases to handle, what to do with an empty list. Each is too small to write down on its own. Together they are most of what the system actually does.
In a human-written codebase those small choices are made by someone holding context. They know what the system is for, they read the morning’s discussion, and their hundred tiny decisions cohere because they come from one situation. AI makes the same kinds of choices in the same numbers without that shared context. Each rests on whatever happened to be in its prompt. Two functions generated minutes apart can quietly assume different things about the same data: one treats an empty result as ordinary and returns a default, the next treats it as impossible and indexes straight in. Both look right where they sit. Neither author knew a fork was being taken, and at the moment it was taken there may have been no human in the loop to notice.
The result is a codebase holding an enormous number of product and engineering decisions, almost none of which can be inspected and fewer still argued with. Ask why the system does something this way and there is nothing to point to. The decision got made, but not by anyone you can ask, and nothing recorded that a decision was made at all. Codebases have always held decisions in people’s heads, imperfectly. This is the first time they hold them nowhere.
Shape Drift
A system has a shape when it answers the same recurring question the same way: error handling looks alike across modules, the data-access pattern is recognizable, the boundary between layers can be felt when you cross it even when no one wrote it down. That consistency is the architecture, whether or not anyone used the word. Drift is what happens when it erodes: the same question gets three different answers in three places, each defensible on its own, and there is no longer a single thing to tell a new engineer who asks how something is done. This is entropy, and every long-lived codebase has drifted.
AI changes the rate. An agent handed a local task writes code that is coherent in the file it lands in and has no way to hold the shape of the whole. It does not know which pattern the team chose unless told, and even when told, matching that pattern often costs more than inventing a fresh one that solves the local problem just as well. So without something pushing back, agent-written code drifts faster than hand-written code ever did. Each decision is small and defensible; the aggregate is a system that loses its shape sooner than its authors expect.
Drift is not corruption. A drifted system still runs; the cost shows up in onboarding, in the time spent working out which pattern applies before any code gets written, and in the price of every change. These are easy to absorb right up until they are not.
I have put this one last on purpose. Shape Drift is largely what the other four look like from a distance. Progress that was not real, changes that spiral, reasons gone missing, decisions no one saw: let them run, and the system they leave behind is one that has lost its shape.
These five are not independent. Mistake throughput for progress and you stop checking; stop checking and the code nobody can safely change piles up, the Progress Mirage feeding the Change Spiral. Missing Reasons and Invisible Decisions are close cousins, one about reasons gone from choices you can see, the other about choices you never saw, and together they are why a codebase stops being able to answer questions about itself. Shape Drift, as the last section said, is mostly what all of it looks like once you step back.
I have separated them because the separation makes them easier to spot. A team with one usually has several, but the way out tends to start with whichever one a given engineer notices first.
| Mode | Symptom you see |
|---|---|
| Progress Mirage | Code output climbing; useful change flat |
| Change Spiral | Touch one thing, two more break; or regions no one will touch at all |
| Missing Reasons | The decisions are visible; the reasons for them are gone |
| Invisible Decisions | Behavior baked in, with no sign a decision was ever made |
| Shape Drift | Several answers to the same question; no single grammar |
Look across the five and the same absence runs through them: a check that never happened. No one checked whether the output was useful, or what a change would touch before making it, or whether the reasoning behind a decision survived in a form anyone could test against the result. That is the case for a new discipline of AI-assisted engineering, one that avoids these five, pursues the four qualities, and builds durable code.