Anthropicがコード移行の6ステップを公開:Bunは100万行を2週間でZigからRustへ
- 本番稼働中のコードベースの言語を完全に切り替えるのは、業界の一般的な見積もりでは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本丸ごと廃止できました。
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の移行を担当したのは、Bunの共同創業者で現在はAnthropicのテクニカルスタッフであるJarred Sumner氏。Python移行は、Instagram共同創業者で現在はAnthropic Labsの共同責任者を務めるMike Krieger氏です。
これまで誰も挑めなかった理由
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本退役させることができました。
同じエラーが繰り返し出たら、ルールを変える
この記事全体で最も価値があるのは、この一文です:
The core insight is that you don't fix the code. You fix the process (loop) that produced the code.核心的な認識:コードを直すのではなく、コードを生み出すプロセス(ループ)を直すのです・ClaudeDevs
これを具体的な行動に落とすとこうなります。レビューAgentが数十のファイルで同じエラーを繰り返し検出した場合、あなたの手はそれらのファイルに向かいません。ルールブックに一文追加し、影響を受けるバッチを再生成します。ルールブックはプロセス全体を通してどんどん厚くなっていきます。しかし、コードに手動でパッチを当てることは決してありません。
Jarred氏は各段階にレビューとゲートを設定しました。Mike氏はさらに徹底した方法を取りました。移行プロセス全体を最初から最後まで実行し、その結果に基づいてルールとワークフローを修正し、再度実行します。各ラウンドの成果物は全て破棄し、3回目にして初めて成果を残しました。
2回分の完全な移行結果はゴミ箱行きとなり、残ったのは改善されたルールだけです。「成果物は消耗品、プロセスこそが資産」という言葉を、文字通り実践した形です。
なぜコード移行にこの方法が特に効くのか
- タスクが自然に並列化できる。ファイルやcrateなど、互いに独立した何千もの単位に分割でき、Agentが互いを待つことなく同時に作業できます。
- コンテキストが明確で完全。旧コード自体が、モデルにとって優れた仕様書になります。
- 判定役が既にいる。ほとんどの大規模コードベースにはテストがあり、Agentはそれを使って自分の作業の正しさを検証できます。
- キューは自然に生成される。コンパイラのエラー、テストの失敗。そのエラーが次のAgentの次のタスクになります。
- 一貫性とエッジケース対応が求められる。レビューは各指摘に対して根拠となるルールを引用するため、一度の違反はプロセスから静かに逸脱するのではなく、キューの1項目になります。
着手前に「判定役」を構築する
コードの翻訳を始める前に、必ずやらなければならないことがあります。それは「判定役」を構築することです。判定役がなければ、終了条件も成功の尺度もなく、いつ移行が完了したのか、正しく移行できたのかが分かりません。
内部実装に依存し移行できないもの
新旧両方で実行できる
壊したコードで失敗すること
1. 既存テストを分類する:Claudeを使って、外部呼び出しで検証できるテストと、内部実装に依存して移行できないテストを特定する。 2. 移植可能な形に書き換える:外部向けテストを、元のコードでも移行後のコードでも実行できるアサーションへと書き換える。敵対的エージェントを使い、書き換えたアサーションが弱体化していないか確認する。 3. 判定役自体を検証する:まず元のコードで実行し、パスすることを確認。次に意図的に壊したコードで実行し、失敗することを確認する。壊れを検出できない判定役は判定役ではない。
2番目のステップにある「敵対的Agentによるアサーションの弱体化チェック」は、任意ではありません。テスト書き換え時にアサーションを少し緩めると、通過率はすぐに良くなります。しかし、失うのは正しさを判断する物差しそのものです。3番目のステップも同様です。何でも通してしまうテストスイートは、判定役のように見えて、実際はただのゴム印です。
ルールブック、依存関係グラフ、ギャップリスト
この3つの順番は絶対に逆にできません。ルールブックはギャップリストよりも先に作成します。なぜなら、ギャップリストの定義は「ルールブックのデフォルトルールでカバーされない部分」だからです。前者があって初めて後者が意味を持ちます。両者は最終的に同じ合同レビューに入れて一緒に検証します。
ルールブックの形は、着手時に下す決断で決まる
新コードは元の構造を踏襲するのか、それとも完全に再設計するのか。この決断によって、ルールブックの形が決まります。
ルールブックの主な内容は対照表です。この型はあちらではこう書く、このイディオムはこちらではこれに対応する、といった具合で、翻訳が難しい部分はギャップリストに誘導します。
Jarred氏はClaudeと対話を重ねながら、曖昧な点が出るたびにそれを1つのポリシーとして制定していきました。
また、自身が遭遇した問題に基づき、一般的な失敗パターンをそれぞれチェックする8つのサブAgentを特別に設定しました。
ルールブック自体が設計ドキュメントであり、翻訳の対照表ではありません。
このアプローチでは「正しく翻訳できているか」という問いは二の次になり、「設計が正しいか」が主要リスクになります。そのため、検証方法も変わってきます(次節で説明)。
依存関係グラフとギャップリスト
依存関係グラフは、並行ワークフローを分割するために使います。どのファイルを先に移行すべきか、どのファイルを同じバッチで移行すべきかを把握する必要があります。手作業ではなく、Claude CodeにAgentを指示して決定論的なスクリプトを書かせ、実行してグラフを生成させます。
ギャップリストには別の種類のものを入れます。新しい言語には必須だが、古い言語には存在しない要件です。ZigからRustへの移行におけるギャップは手動メモリ管理(CやC++と同じ流儀)、PythonからTypeScriptへの移行におけるギャップはインターフェースと契約(コントラクト)です。次節で原文のコードを用いて詳しく見ていきます。
Zigが放置していたことを、Rustは強制する
同じ関数、メモリを確保し、値を詰め、呼び出し元に返す。2つの言語での記述の違いは構文ではなく、誰が後片付けをするかにあります。
// 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.
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つの結末しかありません。本は常に誰か一人の手にあり、図書館は誰かが返却を覚えているかどうかに関係なく、いつ回収すべきかを知っています。Zigの方は「読んだら返却」という付箋があるだけで、忘れても誰も止めてくれません。
PythonからTypeScriptへの移行は、インターフェース契約でつまずきます。Pythonは関数がどんな形のオブジェクトを受け取るか、返すかを宣言する必要がありませんが、TypeScriptは必要です。この情報は元々コードを書いた人の頭の中にしか存在しなかったため、一つずつ掘り起こして書き出す必要があります。これがギャップリストの役割です。
Jarred氏とMike氏は両方ともギャップリストファイルを作成しましたが、タイミングが異なります。Jarred氏は事前にギャップを徹底的に洗い出しました(これは6ステップ法でもステップ1に置かれているプラクティスです)。Mike氏はまず翻訳を進め、その後で振り返って監査し、ギャップを補充しました。あなたも両方のアプローチが必要になるかもしれません。
まず3つのファイルでルールを試す
ルールブックが完成したら、すぐに全体へ展開するのはやめましょう。Jarred氏はこのステップで3つのAgentを起動し、3つのファイルだけを扱わせました。
ルールブックに厳密に従い、3ファイルを翻訳する。
「熟練のRustエンジニアのように」同じ3ファイルを翻訳する。
この2つの翻訳の差分を読み、そこから新しい翻訳ルールを書き出す。
この3ファイルで、彼は2つの致命的な問題を発見しました。今回の移行は合計1,448ファイルです。もし最初から全体に展開していたら、この2つの問題はコードベースの隅々にコピーされ、後から一つずつ回収することになっていたでしょう。
このステップが終わった時点で、翻訳されたファイルはすべて破棄します。目的はルールを磨くことであり、進捗を貯めることではありません。
キューをディスクから再構築、中断しても再開可能
ルールブックの検証が終わり、判定役も揃いました。ここからが本格的な全量翻訳です。このステップ以降、残りの全ステップは同じマルチAgentループ、つまり実装・レビュー・修正を繰り返します。
どのモデルがどの役割を担当するか
実装作業は小さなモデルに任せ、レビューは大きなモデルに任せます。Mike氏はメインの移行段階で、実装用に12のClaude Sonnet サブAgentを並列稼働させました。
キューは機械的であるべき
バッチ処理スクリプトが、あるファイルの処理が完了したかどうかを判断する唯一の基準は、ディスク上に翻訳ファイルが存在するかどうかです。その後、残りのタスクをバッチに分割し、実装Agentに渡します。
中断してもいつでも再開できる
翻訳Agentは自信がない箇所に遭遇した場合、推測や無理な翻訳はせず、一律マーカーを打って、ステップ4で集中的に処理します:
// TODO(port): <reason>
レビュー側は、2つの敵対的レビューがそれぞれ独立したコンテキストで実装結果を評価し、意見が一致しない場合は第3のAgeが裁定します。レビューが複数のファイルで同じエラーを繰り返し検出した場合、ルールブックの一文を修正し、そのバッチを再生成します。
ステップ4・5・6:コンパイル、実行、挙動の一致
この3つのステップは同じループ構造を共有しており、進むほどに人の判断が必要なくなります。
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++境界上の単一行のポインタ操作です。
どのプロジェクトにも当てはまるプラクティス
- このガイドを盲目的にコピーしない。移行は毎回異なります。これを出発点として、着手前にClaudeと一緒にあなた自身の計画を立ててください。
- 単一の失敗にこだわらない。単一の失敗はループに処理させ、あなたの注意はパターンに向けてください。
- レビューは敵対的に、検証は機械的に。コンパイラ、diff、テストスイートなどのスクリプトに判定役を任せてください。
- 常に最大のモデルを使わない。量が多い実装は小モデルに任せ、最大のモデルはレビュー、そして「他のAgentが従うべきルール」を書く場面に使いましょう。
- 人的工数を前へ。ルールブックとストレステストが最も時間を消費します。その後は基本的にキューが処理を進めます。
テンプレートの入手先と、割り引いて読むべき点
この仕組みはゼロから作る必要はありません。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パッケージ全て成功」なのか「成功した事例だけを紹介している」のかは、この資料からは検証できません。
100万行のコードを別言語に、2週間で移行完了:Anthropicが全プロセスを公開
Anthropic公式アカウントが社内のコード移行手順を完全公開。テンプレートとプロンプトもオープンソース化。このページで核となる手法を図解します
↓ 一枚で完結 · 動く図つき
本番環境で動くコードベースを、言語ごと書き換える「コード移行」。Anthropic公式アカウントのClaudeDevsが、社内でこの1ヶ月どう進めたかを公開しました。この1ヶ月で10個のコードパッケージを移行。規模は数万行から数十万行まであり、記事で詳しく扱っているのはそのうち2件です。
この数字はすべてClaudeDevsの記事からの引用です。2つのケースの主役は、Anthropicの技術スタッフと、Anthropic Labs共同責任者のMike Krieger(Instagram共同創業者)。外部による第三者再現はまだありません。
同じことを、前の四半期までにやろうとしても、採算が合いませんでした。
- 期間は4年、エンジニアリソースは300〜400万ドル相当
- その間ロードマップ凍結、新機能はストップ
- 2つのコードベースを並行運用、数四半期〜数年間
- 最悪の結果は「類似度90%」で、移行しないより面倒
- 数万〜数十万ドルの規模。それでも大きな金額だが
- 最悪の結果はブランチを消してやり直すだけ
- 「移行しなければ生き残れない」は必須条件ではなくなった
- 1年間更新履歴に眠っていたメモリ関連パッチや、長年ボトルネックだった問題があれば十分
Mike Kriegerのプロジェクトも、あるボトルネックがきっかけでした。彼のチームの内部ツールは、ユーザーに単一の実行ファイルとして配布する必要があるのですが、Pythonのツールチェーンでは1プラットフォームのビルドに8分もかかっていました。
バーの長さはおおよその所要時間。約2秒のバーは見えるように拡大しています。これはMike Kriegerが週末1つで達成した結果です。16.5万行のTypeScript、コンパイルは8分→2秒に、実行ファイルの起動は6倍高速化。ついでにデプロイパイプライン全体も退役させました。数字はすべて記事からの引用です。
問題が起きたら、コードに手を伸ばすな。コードを生み出すプロセスに手を伸ばせ。具体的にはこうです。レビュー担当Agentが数十のファイルで同じ間違いを繰り返し見つけたら、ルールブックに一文追加して、影響を受けた分をまとめて再生成する。ルールブックはどんどん分厚くなる。コードのほうを手作業でパッチしてルールに合わせることは、絶対にしない。
判定役とは、移行が正しいかを判断する基準です。これがないと終了条件がなく、いつ完了したのかわかりません。元記事でも、これは最初のステップとして挙げられています。
テンプレートとプロンプトはGitHubの「anthropics/code-migration-kit-with-claude-code」で公開中です。ルールブックのテンプレートとビルドスクリプトが含まれています。手法はそのまま使えます。対照表は以下の通りです。
✔ 実装は小規模モデルに、レビューは大規模モデルに
✔ 作業キューは毎回ディスクから再スキャン、翻訳ファイルがあれば完了扱い
✔ 翻訳できない箇所は // TODO(port): 理由 を入れて後で一括対応
✔ 本番展開前に3ファイルでルールを試し、翻訳分は全て破棄してルールだけ残す
✘ どの工程も最大モデルを使う
✘ 進捗をAgentのメモリに依存する(切れたら最初から)
✘ 確信がない箇所に推測を書き込む
✘ ルール未検証のまま1,448ファイル全てに展開
- × 300〜400万ドル
- × ロードマップ凍結4年
- × 2系統のコードを並行運用
- × 移行90%で頓挫
どうやって分かる?
② 判定式に書き換え
③ 故意に壊したコードで検証
同じエラー!
59億の未キャッシュ入力トークン