Skip to content
enterprise developer-transition guides

Bringing AI Coding to a 20-Year-Old Codebase

Most AI-coding advice is written for greenfield demos. Here's a field guide for brownfield work, where the rules change and the failure modes are sharper.

Pierre Sauvignon
Pierre Sauvignon 13 min read
An old layered architecture diagram overlaid with a modern editor cursor

Almost every demo you have seen of an AI coding assistant starts from nothing. An empty folder, a clean prompt, a to-do app conjured in ninety seconds. It looks like magic because it is the easiest possible case: no history, no constraints, no twenty years of decisions that someone made for reasons nobody alive can fully reconstruct.

That is not the codebase most of us work in. The real one has a payments module that three teams have touched, a date-handling function with a comment that just says // don't change this, and a directory whose name stopped describing its contents around 2014. When you point a model at that, the magic gets strange. Sometimes it saves you a day. Sometimes it confidently rewrites a business rule that was load-bearing and you don’t find out until a customer does.

This is a guide for that second world. Not greenfield, not a hackathon. Brownfield. The place where most AI-coding advice quietly breaks down, and where the tools can still earn their keep if you know exactly what to ask of them and what to never trust them with.

Why legacy work is the hard case, not the easy one

Start with a definition. Michael Feathers, in Working Effectively with Legacy Code, offered the one that stuck: legacy code is simply code without tests. His reasoning is blunt. “With tests, we can change the behavior of our code quickly and verifiably,” he wrote. “Without them, we really don’t know if our code is getting better or worse.” That definition matters more in the AI era than it did when he wrote it, because the thing an AI assistant is best at producing — fast, plausible-looking change — is exactly the thing an untested codebase has no way to validate.

Legacy code is hard for a model for the same reasons it is hard for a new hire, only the model has none of the human compensating instincts. A new engineer who senses they don’t understand something gets nervous and asks. A model that doesn’t understand something produces a fluent, well-formatted answer with the same confidence it brings to a problem it has solved a thousand times. The tone never wavers. That uniform confidence is the single most dangerous property of AI in a codebase you don’t fully understand.

And there is hard evidence that the difficulty is real, not just folklore. In July 2025, METR ran a randomized controlled trial with experienced open-source developers working on their own mature repositories — projects averaging over a million lines of code that they had maintained for years. The developers expected AI to speed them up by 24%. Instead, allowing AI tools made them 19% slower. The researchers noted that AI capability “may be comparatively lower in settings with very high quality standards, or with many implicit requirements” — which is a precise description of a long-lived codebase. The very conditions that define legacy work are the conditions under which the early-2025 tools underperformed for the people who knew the code best.

That should reframe how you think about this. The greenfield demo is the model’s best case. Your twenty-year-old codebase is closer to its worst.

The context problem nobody mentions in the demo

The headline limitation is context. A model can only reason about what fits in its window, and a legacy system does not fit. It sprawls across hundreds of files, implicit conventions, database schemas, config that lives in three places, and tribal knowledge that was never written down anywhere a retrieval system could find it.

People assume bigger context windows solve this. They don’t, and there is a well-known reason. The 2023 paper Lost in the Middle: How Language Models Use Long Contexts by Nelson Liu and colleagues at Stanford showed that model performance is highest when the relevant information sits at the very beginning or the very end of the input, and “significantly degrades when models must access relevant information in the middle of long contexts.” Crucially, they observed this even in models built explicitly for long context. Stuffing your whole module into the prompt does not guarantee the model attends to the part that matters. The relevant detail can be sitting right there, in the middle, effectively invisible.

So the failure isn’t only “the codebase is too big to fit.” It is that even when the right code is in the window, attention is uneven, and the part you most needed the model to read carefully may be the part it skimmed. Combine that with a retrieval layer that grabs files by surface similarity rather than semantic dependency, and you get an assistant that is articulate about the wrong context.

The practical consequence: in a legacy setting, you cannot delegate the question “what is relevant here?” to the tool. That judgment is the human’s job, and it is most of the job.

There is a deeper version of this problem too. A legacy system’s behavior is often not contained in its code at all. It lives in a database trigger, a cron job on a server someone forgot about, an environment variable set during a 2019 incident, or the precise order in which two services happen to start. The model sees the source files and reasons as if the source files are the whole truth. They are not. When an AI assistant explains why a function behaves a certain way, it is explaining the code in front of it, not the running system — and in old systems those two things have drifted apart. The gap between “what the code says” and “what the system does” is where the most confident wrong answers live.

Where AI genuinely shines in brownfield work

None of this means keep the tools out of legacy code. It means aim them. There are specific tasks where AI assistance is not just safe but genuinely transformative, and they share a common shape: the model is operating against a clear, checkable specification rather than guessing at intent.

Writing characterization tests

This is the highest-leverage use, and it maps directly onto Feathers’ method. A characterization test does not assert what the code should do. It captures what the code currently does — you run the function, observe the output, and pin that observed behavior in place so you’ll know the instant a future change alters it. It is a safety net, not a specification.

AI is unusually good at this because the work is mechanical and high-volume. Point it at an untested function and ask it to generate tests that exercise the existing behavior across a range of inputs. It will enumerate edge cases a tired human skips, and because the goal is to record current behavior rather than judge it, the model’s lack of domain understanding doesn’t hurt you — the running code is the source of truth, not the model’s opinion of it. This inverts the usual risk. Everywhere else, the model not understanding the domain is a liability. Here, you are explicitly asking it not to have opinions, just to observe. Build that test harness first, and every subsequent change gets a guardrail. We go deeper on this in our guide to testing AI-generated code.

Explaining gnarly code

The second strong use is comprehension. Hand the model a four-hundred-line function with five levels of nesting and ask it to explain what it does, trace a particular variable, or draw the control flow. This is read-only — it changes nothing — so the worst case is a wrong explanation you catch, not a wrong change you ship. Treat the output as a hypothesis to verify against the tests you just wrote, never as gospel. Used this way, the assistant becomes a tireless reading partner that shortens the hours you’d otherwise spend reverse-engineering someone’s 2011 cleverness.

Mechanical refactors and migration scaffolding

The third zone is repetitive, pattern-based transformation: renaming a concept across a hundred files, converting callbacks to async/await, updating a deprecated API call to its replacement, or scaffolding the boilerplate of a framework migration. These are tasks with a clear shape and a clear correctness criterion. The model handles the volume; your tests and your review handle the verification. The key word is mechanical — the transformation rule is known and stated, and the model is applying it, not inventing it.

See how developers track their AI coding

Explore LobsterOne

Where AI hurts in brownfield work

Now the other side of the ledger. The danger zones are the inverse of the strengths: tasks where the spec lives only in someone’s head, or in the accumulated logic of the system itself.

Undocumented business rules

The most expensive failures come from business logic that looks arbitrary but isn’t. The discount that caps at a specific number because of a contract signed in 2018. The retry that waits exactly that long because a downstream vendor rate-limits at a threshold nobody documented. To a model, these read as oddities — code that could be “cleaned up.” It will helpfully simplify them, and the simplification will be wrong in a way that passes every test you have, because you never had a test for the rule in the first place. This is why the characterization-test step is not optional. Before you let AI touch domain logic, the current behavior must be pinned down, or you are flying blind.

Novel architecture

The second danger zone is genuinely new design. When you are deciding how a new subsystem should fit into an existing architecture — what the boundaries are, where state lives, how it talks to the rest of the system — you are making judgment calls that depend on context the model cannot see. This is where Conway’s Law bites: a system’s structure tends to mirror the communication structure of the organization that built it, and a model has no visibility into your org chart, your team’s history, or the political reasons your modules are shaped the way they are. It will propose a clean architecture that ignores all of it. Clean and wrong.

The volume trap

There is also a systemic risk that legacy codebases are especially vulnerable to, and it shows up clearly in the data. GitClear’s 2025 analysis of 211 million changed lines found that the share of copy/pasted code climbed from 8.3% in 2021 to 12.3% in 2024, while refactored (“moved”) lines fell from around 25% to under 10%. For the first time in their records, copy/paste exceeded refactoring. In a fresh project, duplication is a future problem. In a legacy codebase, duplication is how the codebase got into trouble in the first place, and an assistant that prefers pasting over reusing accelerates exactly the decay you are trying to reverse.

The macro picture echoes this. The 2024 DORA report found that as AI adoption rose, teams saw an estimated 7.2% drop in delivery stability and a 1.5% drop in throughput, with 39% of respondents reporting little or no trust in AI-generated code. DORA’s read is that the mechanism is batch size: AI makes it easy to produce more code per change, and larger changesets carry more risk. That finding points straight at the most important tactic for legacy work.

A sequence that works

If you are bringing AI into a legacy codebase for the first time, the order of operations matters as much as the tools. A useful default sequence looks like this.

Begin with the read-only phase. Spend the first stretch using AI purely to understand — explaining functions, tracing data flow, mapping which modules call which. Nothing is at risk because nothing changes, and you build a shared model of the system between yourself and the tool. This is also where you discover how reliable the assistant is on your code specifically, before you have given it the power to break anything.

Then build the safety net. Use the model to generate characterization tests around the areas you intend to touch. You are converting an untested region — legacy code by Feathers’ definition — into a tested one, and you are doing it at a speed that was never practical by hand. Only once the net is in place do you start making changes, and even then you start with the mechanical, low-judgment transforms before working up to anything that touches business logic.

This sequence is deliberately the opposite of the demo workflow, where the first thing you do is generate new code. In brownfield work, generating code is the last thing you do, not the first. Teams onboarding AI into established systems, especially under an engineering manager’s first ninety days, benefit from making this order explicit rather than letting each developer improvise it.

Tactics that actually hold up

Knowing the strengths and danger zones is the strategy. Here is the day-to-day practice that makes it survivable.

Feed it the right context, not all the context. Because of the lost-in-the-middle effect, more context is not better context. Curate. Give the model the specific files and the specific contract it needs, and put the most important material at the start or end of your prompt where attention is strongest. Spend your effort selecting what goes in, not maximizing how much.

Make tests the guardrail, every time. Before any meaningful change to legacy logic, there should be a characterization test capturing current behavior. This is the single highest-value habit in brownfield AI work. It converts the model’s biggest weakness — confident misunderstanding — into a catchable error, because a behavior change now trips a red test instead of reaching production silently.

Keep diffs small and reversible. DORA’s batch-size finding is the whole game here. The temptation with AI is to accept a sweeping change because it compiles and looks right. Resist it. Small diffs are reviewable; large AI diffs are merely skimmable, and skimming is how confident-but-wrong code gets waved through. A change you can read in full and revert in one command is a change you control. This discipline is also your best defense against the doom loops where each AI fix spawns two new problems.

Review for intent, not just correctness. The question in legacy review is not only “does this work?” but “did this preserve a rule I don’t fully understand?” That demands a human who is willing to dig into why the old code was shaped the way it was before approving the new shape. The model can’t ask that question. You have to.

Match the task to the tool’s strengths deliberately. Use AI freely for tests, explanations, and mechanical transforms. Slow down and lead manually for novel architecture and undocumented domain logic. Encoding that distinction into your team’s workflow patterns is what separates teams that get a real boost from legacy AI work and teams that just generate more churn faster.

The Takeaway

The greenfield demo sells a fantasy: point the model at a problem and watch the solution appear. Legacy work punctures it, and the puncture is useful. It forces you to be precise about what these tools are actually good at.

They are extraordinary at mechanical, verifiable, specification-bound work — characterization tests, code explanation, repetitive refactors, migration scaffolding. They are dangerous at the parts of legacy engineering that depend on knowledge the model cannot see — undocumented business rules and novel architecture. The line between those two zones is the entire discipline. The evidence from METR, GitClear, and DORA all points the same direction: in mature, high-constraint codebases, raw AI velocity is not the goal, and pursuing it makes things worse. Pinned behavior, small diffs, and curated context are how the tools become an asset instead of an accelerant for decay.

A twenty-year-old codebase is a record of every decision that kept the business alive. Treat the AI as a fast, fluent, occasionally overconfident new teammate who has never read that record. Give it the right pages, check its work against tests, and never let it rewrite history it doesn’t understand. Do that, and the magic from the demo finally shows up where it was hardest to believe in.

Pierre Sauvignon

Pierre Sauvignon

Founder

Founder of LobsterOne. Building tools that make AI-assisted development visible, measurable, and fun.

Related Articles