To migrate code with AI, split the work in two. Codemods, small programs that rewrite code by its structure, handle the mechanical changes. An agent handles the cases that need judgment. Your test suite decides when each batch is done.
Inventory first, migrate in small batches, and keep the old and new versions side by side until their results match. That is also what the teams behind the best published case studies did.
- Start with an inventory: every file and API the migration touches, counted, so you can track progress.
- Use codemods for one-to-one changes and an agent for anything where the meaning changes.
- Migrate in small batches, and let each batch go green in CI before the next one starts.
- For rewrites and ports, run old and new side by side and compare outputs before you switch.
- Published results are strong but not magic: Airbnb automated 97% of its files, Slack converted 80% of the code in a nine-file sample, and people finished the rest.
Migrating code with AI: what the case studies show
The best evidence comes from teams that wrote up real migrations with real numbers.
| Team | Migration | Approach | Result |
|---|---|---|---|
| Airbnb | About 3,500 React test files, Enzyme to React Testing Library | LLM pipeline with per-file steps, retry loops and prompts of 40,000 to 100,000 tokens | 75% of files in 4 hours, 97% after 4 days of tuning, the rest by hand |
| Slack | More than 15,000 Enzyme tests, same move | Syntax-tree codemod plus Claude 2.1, fed the rendered page structure | Codemod alone 45%, LLM alone 40 to 60%, both together 80% on nine evaluated files. In a wider run, about 500 of 2,300 test cases passed |
| 39 internal migrations over 12 months | Tools find where to change, an LLM writes the edits, developers review | 74% of submitted code changes generated by the LLM | |
| Stripe | 3.7 million lines, Flow to TypeScript | Custom codemod, no LLM | One pull request, with 37,000 type errors suppressed by comments |
The pattern repeats. Nobody pointed an agent at a repository and said “migrate”. Each team broke the work into small units, automated the repetitive part and checked every result with tests or reviewers.
Step 1: plan and inventory the migration
Start with the official migration guide, not the model’s memory. A model trained before the new version shipped will confidently use old APIs. Our prompts for coding agents include a batch migration prompt that makes the agent read the guide first.
Then count the work. A one-line search gives you a burndown number you can track daily. Here it is on a small test suite moving from Jest to Vitest:
grep -rhoE "jest\.[a-zA-Z]+" src | sort | uniq -c 4 jest.fn
2 jest.mock
1 jest.requireActual
1 jest.spyOnSort every item into one of three groups:
Mechanical. A pure rename, where old and new behave the same. Codemod territory.
Patterned. The same shape with a few variants. A codemod plus review, or an agent with examples.
Judgment. The meaning changes, so someone has to think. Agent territory, with close review.
Finally, pick a pilot: three to five files of mixed difficulty. They will expose surprises before you touch the other hundred.
Step 2: codemods versus agents
| Codemod | Agent | Both together | |
|---|---|---|---|
| Best at | Repetitive one-to-one changes | Changes that need context and judgment | Large migrations with a long tail |
| Same input, same output | Always | Not guaranteed | For the mechanical part |
| At 10,000 call sites | Seconds | Slow, and costs tokens | Codemod for bulk, agent for leftovers |
| Typical failure | Misses unusual shapes | Invents APIs or edits tests | Fewer failures, because each covers the other |
Here is the mechanical part of the Jest to Vitest move as an ast-grep rule. It rewrites jest.fn and jest.spyOn, which have direct equivalents in Vitest, and nothing else:
id: jest-fn-and-spyon
language: TypeScript
rule:
pattern: jest.$METHOD($$$ARGS)
constraints:
METHOD:
regex: ^(fn|spyOn)$
fix: vi.$METHOD($$$ARGS)Install it first with npm install --save-dev @ast-grep/cli. The CLI’s npm package is @ast-grep/cli, not ast-grep. This is the real output with ast-grep 0.45.3, followed by the inventory again:
$ npx ast-grep scan --rule jest-to-vitest.yml --update-all src
Applied 5 changes
$ grep -rhoE "jest\.[a-zA-Z]+" src | sort | uniq -c
2 jest.mock
1 jest.requireActualThe leftovers are exactly the judgment calls, and Vitest’s migration guide explains why. In Jest, a mock factory’s return value becomes the module’s default export. In Vitest, the factory must return an object that names each export, so some jest.mock calls convert as they are and others need rewriting. And jest.requireActual becomes await vi.importActual, which is asynchronous, so the code around it changes too. A blind rename would break some of these tests.
The best trick is to let the agent write the codemod. A codemod is short, reviewable and runs the same way every time. A thousand hand edits by an agent are none of those.
We are migrating [from X to Y]. The official guide is at [URL]. Write a codemod with [ast-grep, jscodeshift or OpenRewrite] for the changes that are one-to-one renames. Do not include anything whose behavior differs. Run it on [3 pilot files], show me the diff, and run their tests. Then list every remaining use of the old API, grouped by pattern, with what each one needs.
For Java upgrades, OpenRewrite offers ready-made recipes, such as migrating to Java 21 or 25. For JavaScript and TypeScript, jscodeshift is the long-standing option, and ast-grep works across many languages.
Step 3: migrate in small batches
Before the first batch, make the test suite green and fix any flaky tests. A migration on a red baseline cannot tell you what it broke. If the code has thin tests, write characterization tests first, as described in our guide to refactoring legacy code with AI.
Then repeat a short loop for each batch:
Run the codemod on the batch
One folder or one pattern at a time. Commit the result on its own, so a reviewer sees mechanical changes separately from judgment calls.
Hand the leftovers to the agent
Give it the guide, two already-migrated files as examples and the remaining list. Airbnb found that choosing the right related files mattered more than a perfect prompt.
Validate every file, and retry failures
Run the tests, the type checker and the linter on each file. Feed failures back to the agent for a fixed number of retries, then set the file aside for a person.
Merge only when CI is green
Keep each batch small enough to review. Update the inventory count, and start the next batch.
Airbnb calls its tuning loop “sample, tune, sweep”: take a sample of failing files, improve the prompt or script, then rerun everything that still fails.
Step 4: run old and new side by side
Upgrades and swaps can usually be proven with the test suite alone. Language ports and rewrites need more, because the new code shares no tests with the old one by default.
Dual runs. Send the same inputs to both versions and log every difference. Explain each one before you switch.
Feature flags. Route a small share of real traffic to the new version, with a switch to go back instantly.
One slice at a time. Replace one route, job or module, confirm it matches, then move on.
This is the strangler fig approach: the new system grows around the old one until the old one can be removed. It keeps every step small enough to undo.
FAQ
Can AI migrate a whole codebase on its own?
Not in any published case we found. The best results came from pipelines that split the work into small units, validated each one automatically and sent the hard remainder to people. Plan for a human-finished tail.
Should I use a codemod or an AI agent?
Both. Use a codemod for changes that are identical everywhere, because it is fast and predictable. Use an agent where the meaning changes. Slack’s numbers show the combination beating either one alone.
How long does a migration with AI take?
It depends on size, test coverage and how much of the change is mechanical. For scale, Airbnb migrated about 3,500 test files in six weeks. Google’s developers estimated they spent half the time of earlier manual migrations.
Which coding agent is best for migrations?
Any capable agent that can run your tests and read files at scale will do. Context size and cost per file matter more than small benchmark gaps. Our comparison of the best AI coding agents covers the options.
Read next: spec-driven development for planning the migration itself, or how to run a coding agent in CI for batch jobs.
- Accelerating large-scale test migration with LLMs, Airbnb Engineering, 2025
- Balancing old tricks with new feats: AI-powered conversion from Enzyme to React Testing Library at Slack, Slack Engineering, May 2024
- Migrating code at scale with LLMs at Google, Ziftci and others, April 2025
- Migrating millions of lines of code to TypeScript, Stripe, 2022
- Migrating from Jest, Vitest documentation, accessed September 2026
- ast-grep, ast-grep documentation, accessed September 2026
- Quick start, ast-grep documentation, accessed September 2026
- Migrate to Java 25, OpenRewrite documentation, accessed September 2026




