40 How Anthropic Runs Large-Scale Code Migrations with Claude Code
40.1 Overview
On July 16, 2026, Anthropic published a detailed account of how it uses Claude Code to run large-scale code migrations — translating entire codebases from one programming language to another. The post featured two remarkable case studies and distilled the methodology into a repeatable six-step process that any engineering organization could follow.
The two case studies were striking in their scale and speed:
- Bun: A million-line codebase ported from Zig to Rust in under two weeks, with 100% of tests passing.
- Mike Krieger’s project: 165,000 lines of Python translated to TypeScript over a weekend.
These were not toy demonstrations or carefully curated proofs of concept. They were real, production codebases — the kind that normally take engineering teams months or years to migrate. The post explained exactly how it was done, including the failures, the costs, and the lessons learned along the way.
40.2 Case Study 1: Bun — Zig to Rust
40.2.1 The Challenge
Bun is a JavaScript runtime and toolkit known for its extraordinary performance, achieved through its original implementation in Zig. In 2026, the Bun team decided to migrate the entire codebase to Rust — over one million lines of code.
The motivations for the migration were strategic: Rust’s stronger type system, broader ecosystem adoption, and memory safety guarantees made it a better long-term foundation. But the scale was daunting. A million lines of code, manually migrated, would take a team of engineers months if not years.
40.2.2 The Result
Using Claude Code with the methodology described in this chapter, the migration was completed in under two weeks. All tests passed at the end of the migration. The cost was approximately $165,000 in Claude API usage, driven by:
- 5.9 billion uncached input tokens — the raw volume of code and context processed.
- Extensive use of Opus 4.7 for translation and Sonnet 4.6 for review and fixes.
| Metric | Value |
|---|---|
| Codebase size | ~1,000,000 lines |
| Source language | Zig |
| Target language | Rust |
| Duration | < 2 weeks |
| Tests passing | 100% |
| Uncached input tokens | 5.9 billion |
| Cost | ~$165,000 |
The $165,000 cost sounds significant in isolation, but the context matters. A traditional migration of a million-line codebase would require a team of 10-20 engineers working for 6-12 months — a cost in the millions of dollars in salaries alone, plus the opportunity cost of those engineers not building new features. The Claude Code migration was an order of magnitude cheaper and two orders of magnitude faster.
40.3 Case Study 2: Mike Krieger — Python to TypeScript
40.3.1 The Challenge
Mike Krieger (co-founder of Instagram, CPO at Anthropic) undertook a personal project: migrating 165,000 lines of Python to TypeScript. Unlike the Bun migration, this was done as a solo effort using Claude Code, demonstrating that the methodology scaled down to individual developers, not just large teams.
40.3.2 The Result
The migration was completed over a weekend. The entire 165,000-line Python codebase was translated to TypeScript, with Claude Code handling the bulk of the translation work while Mike directed the process, reviewed output, and made architectural decisions.
This case study was notable because it showed that the methodology wasn’t dependent on a large engineering team or specialized infrastructure. A single developer with Claude Code could accomplish what would previously have been months of work.
40.4 The Six-Step Migration Process
Anthropic distilled the methodology into a repeatable six-step process that worked for codebases of any size:
Step 1: Prerequisites → Step 2: Stress-test rules
│
▼
Step 6: Match behavior ← Step 5: Run tests ← Step 4: Compile
│
▼
Step 3: Translate
40.4.1 Step 1: Prerequisites (Tests and Rulebook)
Before any translation began, two prerequisites had to be in place:
1. A comprehensive test suite. Tests were the ground truth that made automated migration possible. Without tests, there was no way to verify that the translated code behaved identically to the original. The test suite needed to:
- Cover the majority of the codebase’s functionality.
- Be runnable in the target language (or be language-agnostic).
- Produce deterministic, comparable output.
2. A translation rulebook. This was a living document that specified how to translate each construct from the source language to the target language:
# Zig → Rust Translation Rulebook
## Memory Management
- Zig `allocator` parameters → Rust ownership/borrowing
- Zig `GeneralPurposeAllocator` → Rust default allocator
- Manual `free()` → Rust `Drop` trait (automatic)
## Error Handling
- Zig errors (`error{Foo, Bar}`) → Rust `Result<T, Error>`
- Zig `try` → Rust `?` operator
- Zig `catch` → Rust `.unwrap_or()` or `match`
## Data Structures
- Zig `struct` → Rust `struct`
- Zig `union(enum)` → Rust `enum`
- Zig `[]const u8` → Rust `&[u8]` or `&str`
## Concurrency
- Zig async functions → Rust `async fn` (tokio)
- Zig waitgroup → Rust `tokio::task::JoinSet`The rulebook was the single most important artifact in the migration. It encoded the team’s collective decisions about how to handle ambiguous translations. Without it, different agents (or human reviewers) would make inconsistent choices, leading to a patchwork of styles. The rulebook ensured consistency across the entire codebase.
40.4.2 Step 2: Stress-Test the Rules
Before applying translation rules to the entire codebase, the rules were stress-tested on a small sample. The process:
- Select 3 representative files of varying complexity.
- For each file, try 3 different translation approaches (e.g., strict literal translation, idiomatic target-language style, and a hybrid).
- Compare the results against the test suite.
- Refine the rulebook based on what worked.
This step was about calibrating the rules before committing to a full translation. It was much cheaper to discover that a translation rule was wrong on 3 files than on 3,000.
Sample files: [file_A, file_B, file_C]
│
┌───────────┼───────────┐
▼ ▼ ▼
Approach 1 Approach 2 Approach 3
(literal) (idiomatic) (hybrid)
│ │ │
▼ ▼ ▼
Test pass? Test pass? Test pass?
│ │ │
└───────────┼───────────┘
▼
Refine rulebook
40.4.3 Step 3: Translate (Fan-Out Subagents)
The actual translation used a fan-out pattern with subagents:
- A coordinator agent identified all files to translate.
- Files were distributed across multiple translator subagents, each working independently.
- Each translator applied the rulebook and produced target-language code.
- A review-fix loop caught issues: the translator’s output was reviewed, and if it didn’t compile or failed tests, the agent iterated.
# Simplified fan-out translation pattern
files = identify_source_files(source_dir)
translations = []
# Fan out: each file translated by an independent subagent
for file_batch in chunk(files, batch_size=10):
result = subagent.translate(
files=file_batch,
rulebook=rulebook,
target_language="rust",
)
translations.append(result)
# Merge results
write_target_files(translations)The key insight was that the rulebook grew during translation, not before. As translators encountered new patterns not covered by the rules, they proposed additions. A human reviewer (or a dedicated review agent) approved additions, and the updated rulebook was distributed to all translators. This ensured that the codebase stayed consistent even as new translation patterns were discovered.
A critical lesson from the Bun migration: code was never hand-patched. If a translated file didn’t compile or failed tests, the fix was applied by updating the rulebook and re-translating the file — not by manually editing the translated output. This discipline ensured that the rulebook remained the single source of truth and that the final codebase was uniformly generated.
40.4.4 Step 4: Compile
Once all files were translated, the next step was getting the code to compile. This was typically an iterative process:
- Attempt to compile the entire translated codebase.
- Identify compilation errors.
- Feed errors back to translation agents for fixing.
- Repeat until the codebase compiles cleanly.
The build daemon played an important role here. Rather than having each agent independently compile (which was expensive and slow), a build daemon serialized expensive operations like compilation and test runs. Multiple agents could submit build requests, and the daemon ensured they were processed efficiently without redundant work.
40.4.5 Step 5: Run Tests (Parity Harness)
With the code compiling, the parity harness was run. This was the mechanism for verifying behavioral equivalence between the original and translated code:
- Run the original codebase’s test suite against the original code → capture outputs.
- Run the same test suite (translated to the target language) against the translated code → capture outputs.
- Compare outputs — any discrepancy indicated a behavioral difference that needed investigation.
# Parity harness example
./run_parity_harness.sh \
--original-lang zig \
--translated-lang rust \
--test-suite tests/ \
--output-dir parity_results/The parity harness produced a report showing:
- Percentage of tests passing.
- Specific failures and their likely causes.
- Performance comparisons (execution time, memory usage).
40.4.6 Step 6: Match Behavior
The final step was resolving any behavioral differences identified by the parity harness. This involved:
- Diagnosing the root cause of each discrepancy.
- Updating the rulebook to handle the pattern that caused the difference.
- Re-translating the affected files.
- Re-running the parity harness.
This loop continued until 100% of tests passed and behavior was verified to match.
40.5 Key Lessons
40.5.1 Lesson 1: The Rulebook Grows, It Doesn’t Shrink
Throughout the migration, the translation rulebook only grew. Every new pattern encountered, every edge case discovered, every stylistic decision made — all were added to the rulebook. By the end of the Bun migration, the rulebook had grown from an initial set of rules to a comprehensive document covering hundreds of translation patterns.
This was a feature, not a bug. The growing rulebook represented accumulated knowledge that made future migrations faster and more reliable.
40.5.2 Lesson 2: Never Hand-Patch
The discipline of never manually editing translated output was essential. If a file was wrong, the fix was to the rulebook, not the file. This ensured:
- Consistency across the codebase.
- Reproducibility (the same input always produced the same output).
- The rulebook captured all knowledge.
40.5.3 Lesson 3: The Build Daemon Serializes Expensive Operations
Compilation and test execution were expensive — both in time and compute. A build daemon that serialized these operations prevented redundant work when multiple agents were running concurrently. Instead of 50 agents each compiling the codebase independently, the daemon compiled once and shared results.
40.5.4 Lesson 4: Missing Test Suite? Build One First
For codebases without comprehensive test suites (a common situation for legacy code), the first step was to have Claude Code build a test suite for the original codebase before attempting migration:
Original codebase (no tests)
│
▼
Claude Code generates test suite
│
▼
Original codebase + test suite
│
▼
Proceed with migration (Steps 1-6)
This pre-step added time but was essential — without tests, there was no way to verify parity.
40.6 Cost and Token Analysis
The Bun migration’s token usage illustrates the scale of large migrations:
| Metric | Value | Implication |
|---|---|---|
| Uncached input tokens | 5.9 billion | Massive context processing |
| Estimated cost | ~$165,000 | Significant but far cheaper than manual |
| Duration | < 2 weeks | Calendar time, including iteration |
| Tests passing | 100% | Verified behavioral parity |
Prompt caching was essential to managing cost. While uncached input tokens totaled 5.9 billion, prompt caching reduced the effective cost significantly by reusing context across translation calls. Without caching, the cost would have been substantially higher.
40.7 When to Use This Approach
The methodology was not appropriate for every migration. It worked best when:
| Condition | Why It Matters |
|---|---|
| Large codebase (>10K lines) | The overhead of setup is amortized |
| Comprehensive test suite exists | Needed for parity verification |
| Languages are structurally similar | Fewer rulebook edge cases |
| Team can invest in rulebook maintenance | The rulebook is a living document |
For smaller codebases (< 1,000 lines) or migrations between very dissimilar languages, a simpler approach (direct prompting, one file at a time) was more appropriate.
40.8 Key Takeaways
- Large-scale migrations are now routine. Bun’s million-line Zig→Rust migration took under two weeks; Mike Krieger translated 165K lines of Python to TypeScript over a weekend.
- The six-step process is repeatable. Prerequisites → Stress-test rules → Translate → Compile → Run tests → Match behavior.
- Prerequisites are non-negotiable. A comprehensive test suite and a translation rulebook must exist before translation begins — without them, parity cannot be verified.
- Stress-test rules on a small sample first. Translate 3 files using 3 approaches, compare against tests, then refine the rulebook before scaling up.
- Fan-out subagents for translation. Distribute files across multiple translator agents, each applying the rulebook independently, with a review-fix loop.
- Never hand-patch translated code. If output is wrong, fix the rulebook and re-translate — never manually edit. This ensures consistency and reproducibility.
- The build daemon serializes expensive operations. Compilation and test execution go through a daemon to prevent redundant work across concurrent agents.
- The rulebook only grows. Every new pattern, edge case, and decision is added. The growing rulebook represents accumulated migration knowledge.
- Missing tests? Build them first. For legacy code without tests, have Claude Code generate a test suite before attempting migration.
- Cost is significant but orders of magnitude cheaper than manual. Bun cost $165K (5.9B tokens) — far less than the millions a manual migration would require.
- The bottleneck is verification, not generation. Generating translated code is fast; verifying it behaves identically is where most effort goes.