Anthropicがコード移行の6ステップを公開:Bunは100万行を2週間でZigからRustへ

テンプレートとプロンプトはオープンソース化済み。Bunの移行では未キャッシュ入力トークン59億を消費、API価格で約16.5万ドル相当
著者:Anthropic / ClaudeDevs07-22 · 12 分で読了⚑ チュートリアル
🎧 ポッドキャスト版0:00 / --:--
0:00 / --:--
ツールチュートリアル · 小互の解説
1分でわかる要点
  • 本番稼働中のコードベースの言語を完全に切り替えるのは、業界の一般的な見積もりでは4年・300〜400万ドル、しかも途中で頓挫する可能性が高い。Anthropicはこの1ヶ月で10個のコードパッケージを移行完了。Bunの100万行は2週間足らずで、マージ前の既存テストは全てグリーンだった
  • この手法の核心は一言:コードを直すな、コードを生み出すループを直せ、です。同じエラーが何十ものファイルで繰り返し発生する場合、正しい対処はファイルを1つずつ直すのではなく、ルールブックに一文追加して対象を一括で作り直すことです
  • 着手前に「判定役」の構築が必須です。既存テストから新旧両方で実行できるものを選び出し、アサーションに書き換えます。さらに意図的に壊したコードで検証し、壊れを検出できない判定役は判定役ではありません。これがないと「いつ移行完了か」の判断基準がありません。
  • 工程の詳細はそのまま流用可能です。作業キューは毎回ディスクから再計算するので、中断しても再開できます。実装は小モデル、レビューは大モデル。レビューはそれぞれ独立したコンテキストで行い、意見が割れたら第3のAgeが裁定。どうしても翻訳できない箇所は一律 // TODO(port) を打って後回しにします
  • コストと効果の計算も明記されています。Bunは未キャッシュ入力トークン59億+出力トークン6.9億、API価格で約16.5万ドルです。Mike Krieger氏のプロジェクトは移行後、コンパイルが8分から約2秒に短縮、起動が6倍高速化、デプロイパイプラインを1本丸ごと廃止できました。
⚑これはAnthropicが自社モデルの使い方を解説した資料です。2つのケーススタディの主役はどちらも社内関係者で、成果の数字は同社の発表に基づきます。本稿のデータはすべてこの前提でお読みください。
1はじめに

1ヶ月で10個のコードパッケージを移行

100万行のコードを別の言語に移行し、2週間で完了。マージ前には既存のテストスイートが全てCIでグリーンになりました。

これはAnthropic公式アカウントのClaudeDevsが公開した実践記事です。この1ヶ月、社内でClaude Codeを使ってコードベース全体を別の言語に移行した方法を解説しています。プロセス全体をテンプレートごと公開しています。使用モデルはClaude Fable 5、Claude Opus 4.8、そしてClaude Codeのdynamic workflowsです。

過去1ヶ月で、Anthropicの開発者は数万行から数十万行規模のコードパッケージを10個移行しました。記事で詳しく解説されているのはそのうちの2つです。

Bun · Zig → Rust
100万行生成されたコード量、所要期間は2週間未満
100%マージ前のBun既存テストスイートのCI通過率
19件マージ後に発生したリグレッション以前は正常に動いていた機能が、コード変更後に壊れること。「新機能のバグ」とは別物です。、すべて修正済み
6月Rust版がClaude Codeに組み込まれ本番稼働中
Mike Krieger · Python → TypeScript
1週末16.5万行のTypeScriptを移行
数百今回の移行で使用したAgent数
8つステージゲートプロセス全体をいくつかの段階に分け、各段階の終わりにチェックを設け、通過しなければ次へ進めない仕組み。+敵対的レビュー3回
全コマンド最後に新旧両方のコードで各コマンドの出力をdiff照合

Bunの移行を担当したのは、Bunの共同創業者で現在はAnthropicのテクニカルスタッフであるJarred Sumner氏。Python移行は、Instagram共同創業者で現在はAnthropic Labsの共同責任者を務めるMike Krieger氏です。

Jarred Sumner氏の100万行PRのGitHubページ
Jarred氏の100万行PRのGitHubページ。画像出典:ClaudeDevs原文
2前提の変化

これまで誰も挑めなかった理由

Jarred氏が当初Zigを選んだのは、C言語並みのパフォーマンスを出しつつ十分シンプルだったからです。本人の言葉を借りれば、大規模言語モデルがない時代に、オークランドの狭いアパートで一人が1年かけてBunを書くための前提条件でした。シンプルさには代償が伴い、その代償は常に存在していました。BunのCLIツールは現在月間ダウンロード数が1,000万を超え、Claude Code内部でも多用されています。

つい先四半期まで、これらの代償はロードマップを凍結し、複数四半期にまたがるプロジェクトにリソースを注ぐ理由にはなり得ませんでした。

前四半期まで
  • 期間4年、エンジニアリングリソース300〜400万ドル
  • 期間中ロードマップは凍結
  • 2つのコードベースを複数四半期〜数年にわたり並行運用
  • 最悪の結果は類似度90%で、着手前より面倒な状態になること
現在
  • 規模は数万〜数十万ドル程度。それでも決して安くはない
  • 最悪の結果はブランチを削除してやり直すこと
  • 着手理由に「移行しないと生き残れない」は不要
  • changelogに1年載ったままのメモリバグ修正、あるいは長期的なボトルネック解消、これで十分な理由になる

Mike氏のプロジェクトも、まさにこのボトルネックがきっかけでした。彼のチームが開発した内部ツールは単一の実行可能ファイルにパッケージ化してユーザーに配布していました。しかしPythonのツールチェーンはプラットフォームごとのコンパイルに約8分かかり、ビルドマトリクス全体では毎回のリリースに30分待たされていました。TypeScriptへの移行後、同じコンパイルは約2秒になり、実行可能ファイルの起動は6倍高速化、チームは独立したデプロイパイプラインを丸ごと1本退役させることができました。

3核となる原則

同じエラーが繰り返し出たら、ルールを変える

この記事全体で最も価値があるのは、この一文です:

The core insight is that you don't fix the code. You fix the process (loop) that produced the code.核心的な認識:コードを直すのではなく、コードを生み出すプロセス(ループ)を直すのです・ClaudeDevs

これを具体的な行動に落とすとこうなります。レビューAgentが数十のファイルで同じエラーを繰り返し検出した場合、あなたの手はそれらのファイルに向かいません。ルールブックに一文追加し、影響を受けるバッチを再生成します。ルールブックはプロセス全体を通してどんどん厚くなっていきます。しかし、コードに手動でパッチを当てることは決してありません。

ルールブック 各レビュー指摘は、ルールブック内のルールを引用する 一括再生成 実装Agent 小モデル 敵対的レビューA 敵対的レビューB それぞれ独立したコンテキスト 第3のAgeが裁定 意見が割れた場合 影響バッチ・破棄 複数ファイルで同じエラー → ルールブックに一文追加
図解・当サイトが原文の記述に基づき作成。太線がこの記事の要点:エラーは個々のファイルに戻らず、ルールブックに戻る。
単なるレトリックではない

Jarred氏は各段階にレビューとゲートを設定しました。Mike氏はさらに徹底した方法を取りました。移行プロセス全体を最初から最後まで実行し、その結果に基づいてルールとワークフローを修正し、再度実行します。各ラウンドの成果物は全て破棄し、3回目にして初めて成果を残しました。

2回分の完全な移行結果はゴミ箱行きとなり、残ったのは改善されたルールだけです。「成果物は消耗品、プロセスこそが資産」という言葉を、文字通り実践した形です。

なぜコード移行にこの方法が特に効くのか

  • タスクが自然に並列化できる。ファイルやcrateなど、互いに独立した何千もの単位に分割でき、Agentが互いを待つことなく同時に作業できます。
  • コンテキストが明確で完全。旧コード自体が、モデルにとって優れた仕様書になります。
  • 判定役が既にいる。ほとんどの大規模コードベースにはテストがあり、Agentはそれを使って自分の作業の正しさを検証できます。
  • キューは自然に生成される。コンパイラのエラー、テストの失敗。そのエラーが次のAgentの次のタスクになります。
  • 一貫性とエッジケース対応が求められる。レビューは各指摘に対して根拠となるルールを引用するため、一度の違反はプロセスから静かに逸脱するのではなく、キューの1項目になります。
4前提条件

着手前に「判定役」を構築する

コードの翻訳を始める前に、必ずやらなければならないことがあります。それは「判定役」を構築することです。判定役がなければ、終了条件も成功の尺度もなく、いつ移行が完了したのか、正しく移行できたのかが分かりません。

① 既存テストを分類する外部から呼び出して検証できるもの
内部実装に依存し移行できないもの
→
② 移植可能なアサーションへ書き換える同じアサーションが
新旧両方で実行できる
→
③ 判定役自体を検証する元のコードでパスすること
壊したコードで失敗すること
判定役構築の3ステップ
1. 既存テストを分類する:Claudeを使って、外部呼び出しで検証できるテストと、内部実装に依存して移行できないテストを特定する。
2. 移植可能な形に書き換える:外部向けテストを、元のコードでも移行後のコードでも実行できるアサーションへと書き換える。敵対的エージェントを使い、書き換えたアサーションが弱体化していないか確認する。
3. 判定役自体を検証する:まず元のコードで実行し、パスすることを確認。次に意図的に壊したコードで実行し、失敗することを確認する。壊れを検出できない判定役は判定役ではない。

2番目のステップにある「敵対的Agentによるアサーションの弱体化チェック」は、任意ではありません。テスト書き換え時にアサーションを少し緩めると、通過率はすぐに良くなります。しかし、失うのは正しさを判断する物差しそのものです。3番目のステップも同様です。何でも通してしまうテストスイートは、判定役のように見えて、実際はただのゴム印です。

5ステップ1

ルールブック、依存関係グラフ、ギャップリスト

この3つの順番は絶対に逆にできません。ルールブックはギャップリストよりも先に作成します。なぜなら、ギャップリストの定義は「ルールブックのデフォルトルールでカバーされない部分」だからです。前者があって初めて後者が意味を持ちます。両者は最終的に同じ合同レビューに入れて一緒に検証します。

ルールブックの形は、着手時に下す決断で決まる

新コードは元の構造を踏襲するのか、それとも完全に再設計するのか。この決断によって、ルールブックの形が決まります。

元の構造を踏襲・Jarred氏のアプローチ

ルールブックの主な内容は対照表です。この型はあちらではこう書く、このイディオムはこちらではこれに対応する、といった具合で、翻訳が難しい部分はギャップリストに誘導します。

Jarred氏はClaudeと対話を重ねながら、曖昧な点が出るたびにそれを1つのポリシーとして制定していきました。

また、自身が遭遇した問題に基づき、一般的な失敗パターンをそれぞれチェックする8つのサブAgentを特別に設定しました。

完全再設計・Mike氏のアプローチ

ルールブック自体が設計ドキュメントであり、翻訳の対照表ではありません。

このアプローチでは「正しく翻訳できているか」という問いは二の次になり、「設計が正しいか」が主要リスクになります。そのため、検証方法も変わってきます(次節で説明)。

依存関係グラフとギャップリスト

依存関係グラフは、並行ワークフローを分割するために使います。どのファイルを先に移行すべきか、どのファイルを同じバッチで移行すべきかを把握する必要があります。手作業ではなく、Claude CodeにAgentを指示して決定論的なスクリプトを書かせ、実行してグラフを生成させます。

ギャップリストには別の種類のものを入れます。新しい言語には必須だが、古い言語には存在しない要件です。ZigからRustへの移行におけるギャップは手動メモリ管理(CやC++と同じ流儀)、PythonからTypeScriptへの移行におけるギャップはインターフェースと契約(コントラクト)です。次節で原文のコードを用いて詳しく見ていきます。

6ギャップの具体例

Zigが放置していたことを、Rustは強制する

同じ関数、メモリを確保し、値を詰め、呼び出し元に返す。2つの言語での記述の違いは構文ではなく、誰が後片付けをするかにあります。

Zig・誰が解放すべきかはコメントだけが知っている
// Zig

fn readConfig(allocator: std.mem.Allocator) ![]u8 {
const buf = try allocator.alloc(u8, 1024);
// ...fill buf...
return buf; // caller must free this — but only the comment says so
}

// A caller that forgets 'defer allocator.free(buf)' still compiles — the leak only surfaces at runtime.
返されたこのメモリは呼び出し元が自分で解放する必要がありますが、それはコメントにしか書かれていません。呼び出し元が解放コードを忘れてもコンパイルは通ります。メモリリークはプログラム実行時に初めて顕在化します。
Rust・所有権は戻り値とともに移転する
fn read_config() -> Vec<u8> {
    let buf = vec![0u8; 1024];
    // ...fill buf...
    buf // ownership moves to the caller; memory is freed automatically
}

// Use it after it's moved? Free it twice? Neither compiles.
// Forget to free it? There's no free call to forget — drop is automatic.
所有権は戻り値とともに呼び出し元へ移り、メモリは自動的に解放されます。移譲後に使う? 2回解放する? どちらもコンパイルエラーです。解放し忘れる? そもそも「解放」というコードが存在しないので、忘れることもありません。
所有権とは何か

本の貸し出しには、返却か次の人への譲渡か、2つの結末しかありません。本は常に誰か一人の手にあり、図書館は誰かが返却を覚えているかどうかに関係なく、いつ回収すべきかを知っています。Zigの方は「読んだら返却」という付箋があるだけで、忘れても誰も止めてくれません。

PythonからTypeScriptへの移行は、インターフェース契約でつまずきます。Pythonは関数がどんな形のオブジェクトを受け取るか、返すかを宣言する必要がありませんが、TypeScriptは必要です。この情報は元々コードを書いた人の頭の中にしか存在しなかったため、一つずつ掘り起こして書き出す必要があります。これがギャップリストの役割です。

Jarred氏とMike氏は両方ともギャップリストファイルを作成しましたが、タイミングが異なります。Jarred氏は事前にギャップを徹底的に洗い出しました(これは6ステップ法でもステップ1に置かれているプラクティスです)。Mike氏はまず翻訳を進め、その後で振り返って監査し、ギャップを補充しました。あなたも両方のアプローチが必要になるかもしれません。

7ステップ2

まず3つのファイルでルールを試す

ルールブックが完成したら、すぐに全体へ展開するのはやめましょう。Jarred氏はこのステップで3つのAgentを起動し、3つのファイルだけを扱わせました。

Agent 1

ルールブックに厳密に従い、3ファイルを翻訳する。

Agent 2

「熟練のRustエンジニアのように」同じ3ファイルを翻訳する。

Agent 3

この2つの翻訳の差分を読み、そこから新しい翻訳ルールを書き出す。

この3ファイルで、彼は2つの致命的な問題を発見しました。今回の移行は合計1,448ファイルです。もし最初から全体に展開していたら、この2つの問題はコードベースの隅々にコピーされ、後から一つずつ回収することになっていたでしょう。

ステップ2:ルールブックのストレステストのフローチャート
ステップ2のフローチャート。画像出典:ClaudeDevs原文

このステップが終わった時点で、翻訳されたファイルはすべて破棄します。目的はルールを磨くことであり、進捗を貯めることではありません。

この手法が有効なのは、構造を変えない移行の場合のみです。同じファイルの2つの翻訳を比較できるため、差分に意味が出ます。あなたのルールブックが再設計の設計ドキュメント(Mike氏方式)である場合、これに相当する方法は、敵対的レビューに設計ドキュメントを直接攻撃させ、使い捨てのエンドツーエンド演習で検証することです。
8ステップ3〜6

キューをディスクから再構築、中断しても再開可能

ルールブックの検証が終わり、判定役も揃いました。ここからが本格的な全量翻訳です。このステップ以降、残りの全ステップは同じマルチAgentループ、つまり実装・レビュー・修正を繰り返します。

ステップ3:全量翻訳のマルチAgentループ模式図
ステップ3「全量翻訳」のマルチAgentループ。画像出典:ClaudeDevs原文

どのモデルがどの役割を担当するか

実装作業は小さなモデルに任せ、レビューは大きなモデルに任せます。Mike氏はメインの移行段階で、実装用に12のClaude Sonnet サブAgentを並列稼働させました。

第3のAgeが裁定 2つのレビューが対立した時 敵対的レビューA 大モデル・独立コンテキスト 敵対的レビューB 大モデル・独立コンテキスト 実装層・12のサブAgentがClaude Sonnet上で稼働
図解・当サイトが原文の記述に基づき作成。数字はMike氏のメイン移行段階の設定に基づきます。

キューは機械的であるべき

バッチ処理スクリプトが、あるファイルの処理が完了したかどうかを判断する唯一の基準は、ディスク上に翻訳ファイルが存在するかどうかです。その後、残りのタスクをバッチに分割し、実装Agentに渡します。

ディスクをスキャン
→
翻訳ファイルがあれば完了とみなす
→
残りをバッチに分割
→
実装Agentへ送信キューは毎回再計算されるため
中断してもいつでも再開できる

翻訳Agentは自信がない箇所に遭遇した場合、推測や無理な翻訳はせず、一律マーカーを打って、ステップ4で集中的に処理します:

統一された保留マーカー
// TODO(port): <reason>

レビュー側は、2つの敵対的レビューがそれぞれ独立したコンテキストで実装結果を評価し、意見が一致しない場合は第3のAgeが裁定します。レビューが複数のファイルで同じエラーを繰り返し検出した場合、ルールブックの一文を修正し、そのバッチを再生成します。

ここで1つ、記録に値する意見の相違があります。それはコンパイラをどこに置くかです。Mike氏はTypeScriptコンパイラを各ループに組み込みました。1ユニットのチェックが数秒で完了するためです。Jarred氏はコンパイラをループから完全に追い出し、次のステップに回しました。cargoの実行に数分かかるためです。判断基準は「コンパイル1回のコスト」であり、どちらが「正しい」かではありません。

ステップ4・5・6:コンパイル、実行、挙動の一致

この3つのステップは同じループ構造を共有しており、進むほどに人の判断が必要なくなります。

ステップ4〜6:コンパイル・実行・挙動の一致のプロセス模式図
ステップ4・5・6のループ構造。画像出典:ClaudeDevs原文

Jarred氏はオーケストレーションスクリプトを使用し、ワークスペース全体に対してコンパイラを1回呼び出し、エラーリスト全体を取得しました。「修正Agent」がこのリストを並列で処理し、敵対的レビューを組み合わせて、再ビルド、そして再実行します。

エラーリスト自体を確認することは有用で、プロセスの変更が必要なシステム的な問題を浮き彫りにします。Jarred氏は実際に遭遇しました。循環インポートを修正した後、何千ものRustモジュールエラーが発生したのです。Zigの遅延コンパイルは循環インポートを許容しますが、Rustは許容しません。彼の解決策は、ループにロジックを組み込み、依存関係を削除すべきか、移動すべきか、境界をリファクタリングすべきかを自動で分類させることでした。

ステップ5にも機械的な真実の源があります。スモークテストで発生するクラッシュです。ループレベルでの修正方法は、問題を根本原因別にグループ化し、敵対的サブAgentによるレビューにかけることです。

ステップ6は、両方のプログラムの挙動を一致させることです。ファイル翻訳が完了し、コンパイルが通り、スモークテストもパスしました。次に、それらをシャードに分割し、事前の段階で構築したテストスイートを実行します。失敗したものは「修正Agent」が処理し、失敗したテストを両方のコードベースに対して同時に確認させます。修正後、敵対的レビューによるチェックを受けます。

このステップでは、新しい役割が登場します。ビルドデーモンです。プロセス全体で実行可能ファイルの再ビルドを許可されているのは、このデーモンだけです。修正Agentはパッチを書くだけ。デーモンはパッチをまとめて1回再ビルドし、影響を受けるテストを再実行し、結果をフィードバックします。最も高価な操作が直列化され、十数のAgentがそれぞれトリガーするのを防いでいます。

テストスイートがない場合はどうするか

多くの人は、移植可能な既存テストスイートを持っていません。Mike氏もそうでした。彼はClaudeに小さなスクリプトを書かせ、7つの実シナリオを新しい移植版と元のPython版の両方で実行し、結果をdiffしました。失敗したシナリオごとに修正Agentを配置し、7つ全てがパスするまでループを回しました。

さらに彼は一歩進んで、Claude自身にエンドツーエンドテストの設計をさせました。4晩連続で自律実行させ、失敗を修正し、再実行させたのです。そこで検出されたのは、どのシナリオリストも予測できなかった小さな問題でした。

結論:テストスイートがないことは、移行をブロックする理由にはなりません。判定役を継承できないなら、Claudeに作らせましょう。どちらの道でも、あなたの元のコードベースが事実上の標準です。

移行完了後

Jarred氏のBun移行は現在本番環境で稼働しています。代償も明記されています。Rustコードの約4%がunsafeブロック内にあります。主にC/C++境界上の単一行のポインタ操作です。

6,745 → 609 MB
2,000回の反復ビルドを行うベンチマークで、メモリ使用量が前者から後者に削減。チームツールで検出可能なメモリリークは全て修正済み
19%減
LinuxとWindowsでの実行可能ファイルのサイズ
2〜5%高速化
言語間最適化による速度向上。HTTPサービス、next build、tscなどの実負荷をカバー
4%
unsafeブロック内にあるRustコードの割合。これが今回の移行の代償側の数字です

どのプロジェクトにも当てはまるプラクティス

  • このガイドを盲目的にコピーしない。移行は毎回異なります。これを出発点として、着手前にClaudeと一緒にあなた自身の計画を立ててください。
  • 単一の失敗にこだわらない。単一の失敗はループに処理させ、あなたの注意はパターンに向けてください。
  • レビューは敵対的に、検証は機械的に。コンパイラ、diff、テストスイートなどのスクリプトに判定役を任せてください。
  • 常に最大のモデルを使わない。量が多い実装は小モデルに任せ、最大のモデルはレビュー、そして「他のAgentが従うべきルール」を書く場面に使いましょう。
  • 人的工数を前へ。ルールブックとストレステストが最も時間を消費します。その後は基本的にキューが処理を進めます。
6ステッププロセス全体図
6ステッププロセスの全体像。画像出典:ClaudeDevs原文
9材料の入手先と注意点

テンプレートの入手先と、割り引いて読むべき点

この仕組みはゼロから作る必要はありません。Anthropicがテンプレートとプロンプトをオープンソース化しています。

入手可能な材料
リポジトリ:https://github.com/anthropics/code-migration-kit-with-claude-code
ルールブックテンプレート:templates/RULEBOOK.md
ギャップリスト用プロンプト:prompts/02-gap-inventory.md
ビルドデーモンスクリプト:scripts/build_daemon.sh
Jarred氏による詳細な振り返り:https://bun.com/blog/bun-in-rust

いくつか、言葉を明確にしておきます。約16.5万ドルというのは、未キャッシュ入力トークン59億と出力トークン6.9億を、公開されているAPI価格で換算した結果です。未キャッシュとは、重複コンテンツによる割引が適用されない部分を指します。この計算は、Anthropic社内の実際の支出を表しているわけではありません。Mike氏の移行の主要部分は2,700万トークンでした。

資料では「過去1ヶ月で10個のコードパッケージを移行完了」とありますが、詳細に説明されているのはそのうちの2つだけです。残り8つの内容と結果については、一切言及がありません。Bunのマージ後に発生した19件のリグレッションの深刻度や、影響を受けたユーザー数についても記載はありません。

この2つのケーススタディの主役はどちらもAnthropicの関係者です。1人は自社のテクニカルスタッフ、もう1人は自社ラボの共同責任者です。手法自体は検証可能であり、あなた自身が実行すれば成功するかどうか分かります。ただし、「成功率」が「10パッケージ全て成功」なのか「成功した事例だけを紹介している」のかは、この資料からは検証できません。

✅ コード移行 着手チェックリスト
ルールブックテンプレート、ギャップリスト用プロンプト、ビルドデーモンスクリプトはすべて anthropics/code-migration-kit-with-claude-code リポジトリにあります。上記の順序に従えば、すぐに着手できます。
出典
How Anthropic runs large-scale code migrations with Claude CodeAnthropic / ClaudeDevs·x.com·2026-07-21
当サイトからの注記
5枚のスクリーンショットはすべて原文に付属するものです。ループ図とAgent階層図は当サイトが原文の記述に基づき作成した模式図であり、原文の図版ではありません。16.5万ドルは公開API価格での換算結果であり、社内の実支出ではありません。原文では10件の移行プロジェクトのうち2件のみが詳細に解説されています。
↑ 本文ここまで
✓ この記事は全文無料です

メンバーは全文 Markdown のコピー・保存に加え、自分の AI に購読・質問させることができます。

原文を読む ↗
📮 新着解説をメールでいち早く

役に立ちましたか?メールを登録すると、新着解説を公開と同時にお届けします。解除はいつでも。