原典: 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 → RustPython → 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 をうまく捌く。最大のモデルはレビュアーと、他のエージェントが従うルールを書くものに取っておけ。

  • 人間の時間は前倒しせよ。ルールブックとストレステストが最も時間を食う。それ以降はほぼキューの消化である。

  • 作業キューは機械的かつ再開可能にせよ。「完了」は「出力ファイルがディスク上に存在する」を意味すべきである。

そのまま使える具体例

判定役の検証(これを飛ばすと終了条件が嘘になる):

  1. 元のコードに対して走らせる → 通ることを確認
  2. 意図的に壊したコードに対して走らせる → 落ちることを確認

訳せなかった箇所の印:

// TODO(port): <reason>

キューの「完了」の定義:

Done should mean "the output file exists on disk." —— ディスクから毎回再構築するので、中断しても再開できる。

原典で言及されている関連文書