Replacing an entire programming language, this was once tantamount to issuing a two-year military order for a company. Engineers would turn pale at the thought, budget committees would be terrified, and codebases would remain stuck in place year after year. On July 16th, Anthropic came out with an article that turned this old account into a new chapter: In the past month, their internal developers used Claude Fable5, Opus4.8, and dynamic workflows to cleanly migrate 10 code packages, each ranging from tens of thousands to hundreds of thousands of lines, to a new language. This is no longer a hypothetical future scenario—it has already happened to them.

The two most striking cases were like two nails that nailed imagination to reality. Jarred Sumner, co-founder of Bun, used Claude Code to migrate the entire Bun from Zig to Rust, producing a million lines of code in less than two weeks, with 100% of the CI test chain passing before merging into the main branch. After merging, 19 regressions surfaced, which have since all been fixed. The Rust version was released in June via Claude Code. On the other side, Mike Krieger, co-lead of Anthropic Labs, migrated a Python codebase to 165,000 lines of TypeScript over a weekend, including hundreds of proxies, eight stage gates, three rounds of adversarial review, and finally using a parity check to compare every command's output line by line with the original Python version.

image.png

Anthropic distilled this entire approach into one sentence, also the sharpest blade in the article: You are not fixing code, but the process (cycle) that produces it. Once you understand this, those migration projects that were indefinitely postponed are no longer off-limits.

First clarify when and why to act, because the premise of this has changed long ago. Teams usually start a migration because the technical landscape at the project's inception no longer aligns with today's needs: either the original trade-offs that were known at the time have now become problematic, or better paths have emerged, or the original ecosystem is shrinking. Jarred originally chose Zig for its C-level performance and extreme simplicity, especially suitable for a founder in a small apartment in Oakland during the era before large models existed, who spent a year writing Bun. That simplicity had a cost, which he later openly admitted in an article.

Fast forward to 2026, Bun's command-line tool has exceeded ten million monthly downloads and is widely used internally within Claude Code. At the end of last quarter, these costs were still not enough to freeze the roadmap and invest several quarters of resources on a big task. Migrating languages can indeed lead to smaller, faster, and safer systems, but no one had the incentive to pay the price. Engineers also had to weigh the career risks: maintaining two sets of code for years and ending up with only 90% equivalence could be more troublesome than just leaving things as they are.

Now it's different. The worst-case scenario is simply deleting that branch and starting over. Of course, business calculations are still needed. A migration of a million lines does not require burning three or four million dollars in engineer resources as it did in a four-year project, but it still costs several thousand to tens of thousands of dollars. The migration of Bun consumed 5.9 billion uncached input tokens and 690 million output tokens, which amounted to about $165,000 based on API pricing; Mike's main migration also burned 27 million tokens. What really made Mike decide to act was the compilation hurdle. His team delivered internal tools as single binary files, and compiling for each platform with the Python toolchain took about eight minutes, making the entire build matrix take thirty minutes. After the migration, the same compilation took only two seconds, and the binary started six times faster, while also eliminating a separate deployment pipeline.

Why can AI balance this old account? Because large-scale code migration is precisely the ideal scenario for these advanced models. The work is naturally parallelizable, with hundreds or even thousands of independent units like files or crates that can be handled simultaneously without waiting for others. The context is clear and complete, with the old code itself serving as the best specification document for the model, and as the guidebook for the translation agent. Even more cleverly, there's a built-in referee. Large codebases often have a suite of tests that the agent can use to self-score, and when validation is sufficiently objective, the model can focus on the truth for days without someone sitting beside it to judge quality. The queue writes itself: when a compiler or test fails, the next job automatically queues up. The process itself makes deviations impossible to hide; every finding must reference a rule, turning one violation into a to-do item rather than a quiet fork; if an agent hits an edge case, the fix becomes a rule that all subsequent agents must follow. In the two cases below, Mike and Jarred both used Fable in key steps, particularly in a consultant mode that allows multiple models of different levels to work together, minimizing token consumption.

What can actually be reused is the six-step method they summarized, refined from these two migrations, and generalized to fit various languages and scenarios.

Before starting, you need to prepare an impartial referee, otherwise you won't know when to finish or how to measure success. This referee must be able to judge the original code and the target code under the same standard. Test suites written in the original language often depend on internal functions that don't exist in the target code. To create one, first use Claude to classify existing tests: which can be expressed through external calls, and which rely on internal implementations that can't be moved. Then rewrite the external-facing tests into assertions that can run on both the original and the ported versions, and send an adversarial agent to verify that the rewriting doesn't weaken the assertion strength. Finally, run the original code to confirm it passes, and run a deliberately broken code to confirm it fails—only a referee that cannot detect any flaws qualifies as a real referee. Mike's migration of Python to TypeScript created a parity checkboard covering seven real scenarios, where any behavioral change counts as a bug to fix.

The first step is to set rules, draw dependency graphs, and list gaps, laying the foundation for the entire migration. The order matters: the rule manual must come before the gap list, because gaps are defined by what the rule manual's default items don't cover. Both must be jointly audited. What the rule manual looks like depends on your initial core architectural decisions: whether the new code follows the old structure or is completely redesigned. Following the structure (Jarred's approach), the manual is mainly a series of tables translating types and idioms between the two languages, with difficult components pointing to the gap list. Redesigning (Mike's approach), the manual is a design document. Jarred worked it out with Claude, setting policies for each ambiguous area, and even assigned eight sub-agents to monitor specific failure patterns he had listed intuitively. Dependency graphs need to map file dependencies to effectively split the work into parallel streams, knowing which to move first and which to bundle together. Some languages and codebases have built-in manifest files, making this easy, but for older code like C/C++ or Python, dependencies must be manually discovered and drawn. Claude Code can deploy agents to run a deterministic script and generate the graph. Gap lists record the hard differences between the old and new languages: Zig to Rust, the difference lies in manual memory management, where a forgotten buffer might compile but only runtime will reveal the leak; Python to TypeScript, the difference lies in interfaces and contracts, where Python doesn't require declaring an object's shape, while TypeScript requires explicit definitions. Jarred and Mike both documented these implicit knowledge in the gap list files. Jarred clarified everything upfront, while Mike filled in later, so you may need to do both.

The second step is to press the rules and conduct a mini-migration as a trial run. Jarred sent one agent to translate three files according to the manual, another agent to translate three files like a senior Rust engineer, and then a third agent to extract new rules from the two versions' differences. During this phase, he caught two critical issues that could have caused big problems if left until all 1,448 files were translated. This pressure test works only for structure-preserving migrations because the two versions of the same file can be compared line by line. If your manual is a redesign, like Mike's, the equivalent approach is to directly let the adversarial reviewer bombard the design document, and then validate with a disposable end-to-end run. No matter what, all translated files are discarded in this step—the purpose is to refine the rules, not to accumulate progress.

The third step is to translate everything, and the remaining steps all run the same multi-agent loop: implementation, review, and repair. Implementation tasks can be handed to small models, while reviews are left to larger ones. When Mike deployed 12 sub-agents, he used Claude Sonnet. The task queue should be as mechanical as possible: a batch script checks whether translated files are on disk to determine which are completed, then slices the pending tasks and sends them to the implementation agent. Because the queue rebuilds from disk every time, the migration is inherently resumable. Sometimes agents may be too cautious and do too little; the solution is a concise instruction with context that reminds it that the compiler will catch errors. For tasks the translator isn't sure about, add a line // TODO (port): reason, leaving it for the fourth step. From here on, the to-do list writes itself: the compiler finds errors, smoke tests catch crashes, and the test suite reports failures. Two adversarial reviewers score the implementation's work with their own contexts, and if they disagree, it goes to a third agent. When a reviewer repeatedly catches the same type of error across multiple files, the fix isn't patching individual files but adding a sentence to the rule manual to regenerate the affected batches. Throughout this step, the manual keeps expanding, while the code is never manually adjusted against it. Another key design decision is where to place the compiler: Mike placed the TypeScript compiler in every loop because it can check a unit in a few seconds; Jarred kept the compiler completely outside the loop, deferring it to the next step because cargo takes several minutes to run.

Steps four, five, and six are discussed together because they share the same loop and require less human judgment as they progress. Step four, compiling, often integrates into step three depending on the language and scale. Jarred used a choreography script to run the compiler across the entire workspace, and the repair agent scanned the error list in parallel, with adversarial review proceeding simultaneously, rebuilding the build, and repeating this cycle. Reviewing the error list helps identify systemic issues, such as when Jarred fixed the loop dependencies tolerated by Zig's lenient compilation, which resulted in thousands of Rust module errors. He added a piece of logic to classify what to delete, move, or restructure the boundaries, finally containing the issue. Step five and the compiler error list have the same mechanical truth source: crashes from smoke tests. Repair methods are again classified by root cause and handed to adversarial sub-agents. Step six, the conclusion of the story, involves comparing the behavior of the two codebases item by item. Once files are translated, compiled, and smoke-tested, they are split apart, and the pre-prepared test suite is applied to run, with failures handed to the repair agent to review both codebases, and the adversarial reviewer checking their fixes. The loop continues with a build guardian process, the only role allowed to rebuild binaries, with the repair agent only writing patches, and the guardian batching them, rebuilding once, rerunning the affected tests, and feeding back the results, serializing the most expensive operations to prevent multiple agents from triggering them individually. When the same failure appears repeatedly in many tests, the fix should go upstream: modify the rule that generated the bug, and only re-generate the files it touched. Mike's approach is especially worth noting: many developers don't have ready or ported test suites. He had Claude write a small script to run the newly migrated version and the original Python version simultaneously on seven real scenarios, comparing the output differences. Each failed scenario gets a repair agent, and the loop runs until all seven pass. He went further, letting Claude design an end-to-end test suite that ran overnight autonomously, fixing wherever it crashed, and catching any minor issues that none of the scenario lists predicted after running for four nights.

This approach, when run, has several practices that hold up in every project, but Anthropic also warns not to blindly trust this guide. Every migration is different, treat it as a starting point, and discuss your specific plan with Claude before proceeding. Don't focus on single-point failures—they're the work of the loop, and the repair agents will smooth them out for you. Your attention should be on the patterns. Keep the review adversarial and the verification mechanical. Adversarial review can support longer tasks, and the extra token cost is often worth it. Let the compiler, difference comparison, and test suite scripts serve as referees. Don't use the largest model for everything; concentrate the token cost in your loop. Be intentional: small models can handle high-throughput implementation, and assign the largest models to reviewers, as well as anyone who writes the rule manual that other agents will follow. Frontload human hours: the rule manual and pressure testing take the most time, after which it's mostly the queue burning. Make the task queue mechanical and resumable, with completion defined as output files already on disk.

Returning to the result itself, not the code details. Jarred's Bun migration has gone live in production. Of course, every migration has trade-offs, with about 4% of the Rust code remaining in unsafe blocks, mainly single-line pointer operations on C/C++ boundaries. But the new codebase is visibly better: memory leaks detected by the team's tools have all been fixed. A benchmark test that repeated a build 2,000 times saw memory usage drop from 6,745 megabytes to 609 megabytes. The binary was 19% smaller on Linux and Windows. Cross-language optimization made it 2-5% faster on real-world loads like HTTP services and next build, tsc.

Those codebases you've been tolerating for a long time, maybe it's time to reassess the cost-benefit. Pick the one you've put up with the longest, ask Claude, and see what a migration to it actually looks like.