原典: How Anthropic runs large-scale code migrations with Claude Code
一行要約
大規模なコード移行の実践記。中心にある一文がすべてを規定している。個々のコードを直すのではなく、そのコードを生んだループを直す。
The core insight is that you don’t fix the code. You fix the process (loop) that produced the code.
中核的な洞察は、コードを直すのではないということだ。そのコードを生んだプロセス(ループ)を直す。
要点
実績と実測値
Anthropic の開発者が1ヶ月で10個のパッケージ(数万〜数十万行)を移行した。2つの事例が詳述される。
| Bun(Jarred Sumner) | 内部ツール(Mike Krieger) | |
|---|---|---|
| 移行 | Zig → Rust | Python → TypeScript |
| 規模 | 100万行を2週間未満 | 165,000行を週末に |
| 判定役 | 既存のテストスイート(第三の言語 TypeScript で書かれていた) | 7つの実シナリオの parity harness |
| トークン | 未キャッシュ入力 59億 / 出力 6.9億(API 価格で約 $165,000) | 主要部分で 2,700万 |
| 結果 | CI で既存テスト 100% 通過。マージ後に19件のリグレッション(修正済み) | コンパイル 8分 → 約2秒、起動 6倍速く、デプロイパイプライン1本を廃止 |
Bun 側の最終的な改善: メモリリーク検出可能なものはすべて解消(2,000回連続ビルドのベンチマークで 6,745 MB → 609 MB)、バイナリは Linux / Windows で 19% 小さく、HTTP 配信や next build / tsc などの実負荷で 2〜5% 高速。代償として Rust コードの約 4% が unsafe ブロック内にある(大半は C/C++ 境界の1行のポインタ操作)。
なぜ今できるようになったか
失敗の代償が変わったことが大きい。
Now, the worst case scenario is you delete the branch and try again.
今や最悪の場合でも、ブランチを消してやり直すだけである。
大規模移行がこの手法に向く理由として5つ挙がる。並列であること、旧コードが仕様書として完璧であること、判定役が組み込みで存在すること(テストスイート)、キューが自動で伸びること、そして一貫性と例外処理を要すること。3つ目の理由づけが効いている。
Agents perform their best when verification is objective, because the model can grind against a ground truth for days without a human arbitrating quality.
検証が客観的なときにエージェントは最も力を発揮する。人間が品質を裁定しなくても、モデルは ground truth に向かって何日でも回し続けられるからだ。
前提条件 — 判定役を先に作り、壊して確かめる
A prerequisite before starting on your migration project is to have a strong judge in place, otherwise you won’t have an exit condition or measure of success.
移行プロジェクトを始める前の前提条件は、強い判定役を用意しておくことである。さもなければ終了条件も成功の尺度も持てない。
作り方は3段階。既存テストを「外部呼び出しで表現できるもの」と「移植されない内部依存のもの」に分類し、前者を両方の言語で走る表明に書き直し(敵対的エージェントで表明が弱まっていないか検証する)、最後に判定役自身を検証する。
Validate the judge. Run it against the original code to confirm it passes. Then run it against deliberately broken code to confirm it fails — a judge that doesn’t catch breakage isn’t a judge.
判定役を検証せよ。元のコードに対して走らせて通ることを確認する。次に意図的に壊したコードに対して走らせて落ちることを確認する —— 破壊を捕まえない判定役は判定役ではない。
Step 1 — ルールブック・依存マップ・ギャップ台帳
The order matters: the rulebook must come before the gap inventory. The gap inventory is defined by what the rulebook’s defaults won’t cover, and the two are tested together in a joint audit.
順序が重要である。ルールブックはギャップ台帳より先に来なければならない。ギャップ台帳は「ルールブックの既定が覆わないもの」として定義され、両者は合同監査で一緒に検証される。
ルールブックの形は、新コードが同じ構造を保つか全面再設計かで変わる。構造保存なら型とイディオムの対応表、再設計なら設計文書になる。
Step 2 — ルールをストレステストする(訳したファイルは捨てる)
小規模な「慣らし航海」を行う。Jarred はここで、ルールブックで訳すエージェント・「シニア Rust エンジニアのように」訳すエージェント・その差分から新しいルールを作るエージェントの3体を使い、1,448ファイルに展開していたら大量の問題を生んでいた2件の重大な問題を捕まえた。
Regardless, throw out any translated files. The goal is to refine the rules, not make incremental progress.
いずれにせよ、訳したファイルは全部捨てる。目的はルールを磨くことであって、少しずつ前進することではない。
なお、この形のストレステストが効くのは構造保存の移行だけ。再設計なら等価な検査は「設計文書そのものを敵対的レビュアーで叩き、使い捨ての end-to-end 実行で検証する」ことになる。
Step 3〜6 — 同じループを回す
以降は implement → review → fix の同一構造を繰り返す。ここに実務的な判断がいくつも埋まっている。
モデルは使い分ける。 実装の fan-out は小さいモデルに任せ、レビュアーは大きいモデルに置く。Mike は主要な移行で 12 の subagent を撒いたとき Sonnet を使った。
キューは機械的にする。
Because the queue is rebuilt from disk every time, the migration is resumable by construction.
毎回ディスクからキューを再構築するので、移行は構造的に再開可能になる。
「完了」の定義は「訳出ファイルがディスク上に存在すること」。
From here on, the to-do lists write themselves: the compiler enumerates the errors, the smoke tests find the crashes, the suite reports the failures.
ここから先、to-do リストは自分で自分を書く。コンパイラがエラーを列挙し、スモークテストがクラッシュを見つけ、スイートが失敗を報告する。
同じ間違いが繰り返されたら、コードではなくルールを直す。 これが冒頭の「コードではなくループを直す」の具体形。
When a reviewer keeps catching the same mistake across files, the fix isn’t per-file. You add one sentence to the rulebook and regenerate the affected batch. The rulebook keeps growing through this step; the code never gets hand-patched against it.
レビュアーが複数のファイルで同じ間違いを捕まえ続けるなら、修正はファイル単位ではない。ルールブックに1文足して、影響を受けたバッチを再生成する。この段階を通じてルールブックは伸び続ける。コードがルールに対して手で当てられることは決してない。
コンパイラをループのどこに置くかは、コストで決まる。 ここは2人の判断が割れていて、その理由が明示されている。Mike は TypeScript コンパイラをすべてのループの内側で走らせた(1ユニットを数秒で検査するから)。Jarred はコンパイラをループから完全に締め出し次の段階へ回した(cargo は数分かかるから)。
最も高価な操作は直列化する。 テストスイート段階では build daemon がバイナリを再ビルドできる唯一のプロセスになる。
Fixers write patches; the daemon batches them, rebuilds once, re-runs the affected tests, and feeds the results back. This serializes the most expensive operation instead of letting multiple agents trigger it independently.
fixer はパッチを書く。daemon がそれらをまとめ、1度だけ再ビルドし、影響するテストを再実行して結果を返す。これは最も高価な操作を直列化するもので、複数のエージェントが独立にそれを起動するのを防ぐ。
テストスイートが無くても止まらない。 Mike は7つの実シナリオを両方のコードベースに対して走らせて差分を取り、さらに Claude 自身に end-to-end のテストスイートを設計させ、4晩連続で自律的に走らせて壊れたものを直させた。
The lesson is that a missing test suite doesn’t block this step. If you can’t inherit a referee, have Claude build one. Your original codebase is the ground truth either way.
教訓は、テストスイートが無いことはこの段階の障害にならないということだ。審判を継承できないなら、Claude に作らせればいい。いずれにせよ元のコードベースが ground truth である。
全プロジェクトで持ちこたえた6つの実践
Don’t follow this guide blindly. Each migration is different. Treat this as a starting point, and plan your specific migration with Claude before committing to it.
Don’t focus on individual failures. Individual failures are the loop’s job. Fixer agents burn those down. Your attention belongs on the patterns.
Make review adversarial and verification mechanical. Adversarial review allows for longer running tasks and is often worth the token consumption. Let scripts — a compiler, a diff, a test suite — be the referee.
Don’t use the largest model for everything. Token spend concentrates in your loops, so design them deliberately. Smaller models handle the high-volume implementation fan-out well; save your largest model for reviewers and for anything that writes rules other agents will follow.
Front-load the human hours. The rulebook and the stress test are the most time-consuming. Everything after is mostly queues burning down.
Make the work queue mechanical and resumable. Done should mean “the output file exists on disk.”
このガイドを盲目的に従うな。移行はそれぞれ違う。出発点として扱い、着手前に自分の移行を Claude と計画せよ。
個々の失敗に注目するな。個々の失敗はループの仕事だ。fixer エージェントが焼き払う。見るべきはパターンである。
レビューは敵対的に、検証は機械的にせよ。敵対的レビューは長時間タスクを可能にし、トークン消費に見合うことが多い。スクリプト(コンパイラ、差分、テストスイート)に審判をさせよ。
何にでも最大のモデルを使うな。トークン消費はループに集中するので、意図をもって設計せよ。小さいモデルは大量の実装 fan-out をうまく捌く。最大のモデルはレビュアーと、他のエージェントが従うルールを書くものに取っておけ。
人間の時間は前倒しせよ。ルールブックとストレステストが最も時間を食う。それ以降はほぼキューの消化である。
作業キューは機械的かつ再開可能にせよ。「完了」は「出力ファイルがディスク上に存在する」を意味すべきである。
そのまま使える具体例
判定役の検証(これを飛ばすと終了条件が嘘になる):
- 元のコードに対して走らせる → 通ることを確認
- 意図的に壊したコードに対して走らせる → 落ちることを確認
訳せなかった箇所の印:
// TODO(port): <reason>キューの「完了」の定義:
Done should mean "the output file exists on disk." —— ディスクから毎回再構築するので、中断しても再開できる。
原典で言及されている関連文書
- a-harness-for-every-task — dynamic workflows。この移行の各ループがこれで組まれている
- building-multi-agent-systems — 検証専任 subagent と「早すぎる勝利」問題
- claude-code-best-practices — 「作る者と判定する者を分ける」の基礎