Refactoring legacy code with AI: what actually works at scale
Jul 20, 2026 · 10 min read · By Daniel Reyes, Engineering
Refactoring legacy code with AI works, but the deciding factor is your test coverage, not the model you pick. An agent can read a 200,000 line codebase faster than any engineer and will happily restructure it. Whether that restructuring preserved behavior is a question only a test suite can answer. Teams that get value here invest in the safety net first and the agent second.
That is the whole argument, and the rest of this is how to sequence it.
Why legacy code is a good fit for an agent
The expensive part of legacy work is not writing the replacement. It is reconstructing what the current code does and why, usually without the person who wrote it. An engineer new to a service spends days building that mental model. An agent reads the repository in minutes.
This inverts the usual intuition about where AI helps. Greenfield work rewards taste and judgment, which is where models are weakest. Legacy work rewards patient, thorough reading of code that already exists, which is where they are strongest. The agent is not being creative; it is being exhaustive.
Concretely, the cases that pay off:
- Mechanical migrations. A deprecated API replaced across 400 call sites. Tedious, error prone by hand, well specified.
- Framework and language version upgrades. Where the change pattern is known and the volume is the problem.
- Breaking up a large file or module where the seams are visible in the code.
- Adding tests to untested code, which is the highest-value first step and is discussed below.
- Standardizing patterns that drifted across years and authors.
Where it fails
Being specific about the failure modes is more useful than a list of benefits.
No tests, no safety net. This is the big one. A refactor is defined by preserving behavior, and without tests you have no way to know whether behavior was preserved. An agent refactoring untested code produces a large diff and a feeling. That is worse than not refactoring, because the diff is too big to review line by line and you will approve it on vibes.
Architectural rewrites. Deciding that a monolith should become four services is a judgment call informed by team structure, roadmap, and operational reality. An agent can execute a decomposition you designed. It should not be the one designing it.
Behavior that was never actually correct. Legacy systems accumulate bugs that downstream consumers now depend on. An agent that "fixes" one during a refactor has changed behavior that something else relies on. Tests written from current behavior protect you here, which is another argument for writing them first.
One enormous pull request. A 3,000 line refactor cannot be meaningfully reviewed by a human, so it gets rubber stamped. The value of review collapses at scale, and any process that produces unreviewable diffs has quietly removed its own safety net.
How to sequence the work
- Characterize before you change. Point the agent at the module and have it write tests describing what the code does today, bugs included. These are characterization tests, and they are the safety net everything else depends on. This is the single highest-value use of an agent on legacy code.
- Review those tests carefully. This is the step people skip. If the tests encode a misunderstanding, every later refactor inherits it. Read them as a specification of current behavior.
- Refactor in small, single-purpose passes. One concern per pull request. Rename in one, extract in another, change the API in a third. Each stays reviewable.
- Keep the suite green as the gate. A failing suite stops the change. Behavior preserved is not a claim, it is a test run.
- Ship behind a flag when the change is risky, so a rollback is a config change rather than a revert and a deploy.
Agentcode is built around exactly this loop: it works against your real test suite, refuses to open a pull request on a failing build, and delivers each pass as a reviewable PR on GitHub or GitLab. It never merges on its own, which matters more on legacy code than anywhere else, because that is where surprises live. See how test-backed refactoring works in practice.
A note on measuring the win
Modernization projects get cancelled halfway through more often than they get finished, usually because nobody could show progress in terms a budget holder recognized. Pick a number before you start and track it: build time, incident count in the touched service, time for a new engineer to make their first change, or test coverage on the modules in scope.
Infrastructure cost is often the most legible one, since a modernization pass frequently changes what the service costs to run, and having a read-only view of what that infrastructure actually spends turns an engineering argument into a financial one. That is usually what keeps the work funded past the first quarter.
Frequently asked questions
Can AI refactor code without breaking it?
Only if a test suite is verifying behavior. Refactoring means changing structure while preserving behavior, and tests are the only mechanism that confirms the second half. With good coverage, an agent refactoring against a green suite is reliable and fast. Without coverage, it is a large unverified diff. Write characterization tests first.
Is AI good at understanding legacy code?
Unusually good, because the task is thorough reading rather than judgment. An agent can trace call paths across a large unfamiliar codebase in minutes and explain what a module does. It cannot tell you why a decision was made in 2014 or which consumers depend on a quirk, so architectural judgment stays with your engineers.
How large should an AI refactoring pull request be?
Small enough that a human genuinely reads it, which in practice means one concern per PR and usually a few hundred lines at most. Large refactors should be split into sequential passes. An unreviewable pull request has removed the control that made the process safe in the first place.
If the legacy work you are facing is more about defects than structure, the same test-first logic applies to fixing bugs with a regression test as proof. For teams running this across many services at once, see coordinating an agent across multiple repositories.