「書き直すには大きすぎる」が崩れた
レガシーコードの全面書き換えは、これまで「やったら死ぬ」案件の代表格だった。工数が読めず、途中で止められず、終わっても品質が保証できない。だから多くのチームは、腐ったコードベースを抱えたまま塩漬けにする。
2026年7月16日、Anthropicがエンジニアリングブログに「How Anthropic runs large-scale code migrations with Claude Code」を公開した。これは抽象的な提案ではなく、実際に完了した2件の大規模移行から抽出された6ステップの手順書である。しかも片方は、JavaScriptランタイムのBunが53万行のZigコードをRustに移植したという、外部から検証可能な公開事例だ。
同時に、この事例はZig作者から「レビューされていないslop(粗悪品)」と公然と批判されてもいる。本記事では、まず何がどう行われたのかを一次情報の数字で押さえ、そのうえで批判が突いている急所を検討する。
事例1: Bunの53万行Zig→Rust移植
Bunの共同創業者でAnthropicのMember of Technical StaffでもあるJarred Sumnerは、2026年7月8日に公開した記事「Rewriting Bun in Rust」で移行の詳細を数字とともに開示した。
| 項目 | 実績値 |
|---|---|
| 移行元 | Zig 535,496行(コメント除く)、1,448ファイル |
| 生成された差分 | +1,009,272行のRust |
| 期間 | 11日間(5月3日〜14日) |
| コミット数 | 6,502件(マージ含め6,778件) |
| ピーク速度 | 約1,300行/分、1時間で695コミット |
| 並列度 | 4つのworktreeにまたがり最大約64エージェント |
| モデル | Claude Fable 5(当時プレリリース) |
| APIコスト | 標準価格換算で約16万5,000ドル |
トークン消費の内訳も出ている。非キャッシュの入力が59億トークン、出力が6億9,000万トークン、キャッシュ読み出しが720億トークンである。キャッシュ読み出しが入力トークンの12倍を超えている点は、この種のループを設計するうえで重要な示唆になる。移行作業の実体は同じルールブックと同じ周辺コードを何度も読み直すことであり、そこがキャッシュに乗るかどうかでコストが一桁変わる。
なぜRustだったのか
動機は流行ではなく、具体的なバグのクラスだった。SumnerはZigの手動メモリ管理とJavaScriptのガベージコレクタが混在することが「安定性問題の主要な発生源」だったと述べている。実際、バグ一覧の大きな割合をuse-after-free、double-free、そしてエラー境界での解放漏れが占めていた。これらは安全なRustでは、そもそもコンパイルエラーとして弾かれる種類の問題である。
移行後の実測値も公開されている。バイナリサイズはLinuxとWindowsで約20%縮小し、Bun.build()を2,000回繰り返したときのメモリ使用量はv1.3.14の6,745MBから609MBで安定するようになった。スループットは2〜5%改善、HTTPリクエスト処理は2.8〜4.8%増。v1.3.14からv1.4.0の間に128件のバグが修正されている。
検証に何を使ったか
この規模の生成コードで最も重要なのは、何をもって「動いた」と判定したかである。Bunのテストスイートはアサーション数でDebian環境が1,386,826件、macOS arm64が1,259,953件、Windowsが1,007,544件、テストファイルは全プラットフォームで約6万件ある。Sumnerによれば、マージ前に6つのプラットフォーム全部でこれを100%通し、スキップも削除もしていない。
それでもマージ後に19件のリグレッションが出た(いずれも修正済み)。100万行超の差分に対して19件という数字をどう読むかは、後述する批判の論点そのものになる。
事例2: Mike KriegerのPython→TypeScript移行
もう一件はAnthropic Labs共同リードのMike Kriegerによる、PythonからTypeScriptへの16万5,000行の移行だ。所要時間は週末1回分。投入されたのは数百のエージェント、8つのフェーズゲート、3ラウンドの敵対的レビュー、そして最終的な出力パリティチェックである。
Bunと違い、こちらには移植できる既存テストスイートがなかった。そこでチームは実世界のシナリオ7件からなるパリティハーネスを作り、元のPython版とTypeScript版でコマンド出力を突き合わせ、挙動の差分はすべてバグとして扱う方針を採った。テストがないなら、テストの代わりになる判定器を先に作る、という判断である。
Anthropicが抽出した6ステップ
ブログが提示する手順は次の6段階だ。順番に意味がある。
- ルールブック、依存関係マップ、ギャップ目録を作る
- ミニ移行でルールをストレステストする
- 並列で全部翻訳する(実装エージェントとレビューエージェントのループ)
- コンパイルし、エラーを潰す
- スモークテストを走らせる
- 元のコードベースと挙動を突き合わせる
ステップ0にあたる「判定器」
手順に入る前の前提条件として、ブログは強い判定器(judge)を先に用意せよと明言している。理由は明快で、判定器がなければ「終了条件も成功の尺度も持てない」からだ。
判定器の作り込みには具体的な作業がある。既存テストを外部から呼び出せるものと内部実装に依存するものに分類し、言語をまたいで移植可能な形に書き直す。そして判定器自体を検証する。元のコードに対して通ることと、意図的に壊したコードに対して落ちることの両方を確認する。この2つ目を飛ばすと、何も検出しない判定器を握って安心するという最悪の状態になる。
ルールブックとミニ移行
ルールブックの中身は移行の性格で変わる。Zig→Rustのように構造を保つ移行なら、型とイディオムの対応表が主体になる。Python→TypeScriptのように設計をやり直す移行なら、それは設計文書になる。
面白いのはステップ2のミニ移行のやり方だ。3体のエージェントを使う。1体目はルールブックに従って3ファイルを翻訳する。2体目は同じ3ファイルを「シニアRustエンジニアのように」翻訳する。3体目はその差分を読んで新しい翻訳ルールを作る。そして、翻訳されたファイルはすべて捨てる。ブログは「目的はルールの洗練であって、作業を少し前に進めることではない」と釘を刺している。
コードではなくループを直す
この方法論の中核にある一文はこれだ。あなたが直すべきはコードではなく、そのコードを生んだプロセス(ループ)である。
具体的な運用として、Jarredはよくある失敗モード8種類それぞれに対応する8体のレビュー用サブエージェントを使っている。そしてレビュアーが同じミスを複数ファイルで繰り返し指摘し始めたとき、対処はファイル単位の修正ではない。ブログの表現では、修正は上流に移る。バグを生んだルールを1文修正し、そのルールが触れるファイルだけを再生成する。
100万行の生成物を1ファイルずつ人手で直すのは不可能だが、ルールを1行直して再生成するのは可能である。この非対称性が、規模を成立させている。
運用上の3つの助言
ブログが挙げる実務的な注意点のうち、特に効くのは次の3つだ。
- 人間の時間を前倒しする。ルールブックとストレステストが最も時間を食う。その後は基本的にキューを燃やすだけになる。
- 全部に最大のモデルを使わない。トークン消費はループに集中するので、ループは意図をもって設計する。
- 作業キューは機械的で再開可能にする。完了の定義は「出力ファイルがディスク上に存在すること」にする。
3つ目は地味だが重要だ。数千単位の並列作業では、途中で落ちること自体は前提であり、再開可能でなければ規模が成り立たない。
移行がエージェント向きなのは、作業が数千の独立単位(ファイルやクレート)に分割でき、旧コードがそのまま仕様書として使え、テストという客観的な検証手段があり、さらにコンパイルエラーやテスト失敗が次のタスクを自動生成してくれるからだ。この4条件が揃わない仕事に、同じやり方をそのまま持ち込むと破綻する。
Zig作者からの反論
ここまでは成功譚だが、この事例には正面からの批判がある。2026年7月14日のThe Registerの記事で、Zigの作者Andrew Kelleyがこの書き換えを痛烈に評した。
Kelleyの論点は主に3つある。第一に、品質問題はAI以前からのものだという指摘で、Sumnerは「LLMを手にするずっと前からslopを書いていた」と述べている。第二に、レビューされないAI生成コードは「この先いくつもの問題を引き起こす」という懸念で、Zigプロジェクト自体がAIによる貢献を受け付けない方針を取っている理由もそこにあるとした。
そして第三が、技術的に最も鋭い問いだ。Kelleyはこう問うている。テストスイートは「Zigコードのバグを捕まえるには不十分なのに、レビューされていない100万行のslopのバグを捕まえるには十分だというのか」。彼は最終的に、これは言語の優劣ではなく「2つのプロジェクトの価値観の乖離」の問題だと総括している。
この批判は無視できない。なぜなら、Anthropicの方法論そのものが、判定器の質にすべてを賭ける構造だからだ。ルールブックも敵対的レビューもパリティハーネスも、最終的には「テストが通ったかどうか」に帰着する。テストが捕らえられない種類の欠陥は、この体制では原理的に検出されない。マージ後の19件のリグレッションは、まさにその隙間から出てきたものである。
公平に見れば、双方の主張は両立する。130万件超のアサーションを6プラットフォームで100%通したことは、任意のAI生成コードとは比較にならない検証水準だ。同時に、テストカバレッジが捕捉しない領域が存在することもまた事実であり、100万行の差分を人間が読んでいない以上、その領域は完全に未検査のまま残る。移行前のZigコードも同じテストで守られていたのだから、判定器の網目の粗さは移行の前後で変わっていない、という見方もできる。
要するに、この手法が保証するのは「移行前と同じ品質基準を満たすこと」であって、「良いコードであること」ではない。何を保証していないかを理解したうえで採用するなら、有用な道具である。
自分のプロジェクトで試す前に、判定器の実力を測っておくこと。既存テストを意図的に壊したコードに対して走らせ、どれだけ落ちるかを見る。ここで多くが素通りするなら、移行を始める前にやるべきはテストを書くことであって、エージェントを並べることではない。
Claude Code側の対応
この規模のワークロードは、ツール側にも跡を残している。Claude Codeのチェンジログを見ると、v2.1.202で動的ワークフローの規模を制御する「Dynamic workflow size」設定が追加され、v2.1.212では/forkが会話を新しいバックグラウンドセッションに複製する挙動に変わり、セッション内でサブエージェントを起動する従来の機能は/subtaskに分離された。
同じv2.1.212では、暴走対策の上限も入っている。サブエージェント生成はセッションあたり既定200件(CLAUDE_CODE_MAX_SUBAGENTS_PER_SESSIONで変更可)、WebSearch呼び出しも既定200件(CLAUDE_CODE_MAX_WEB_SEARCHES_PER_SESSION)で頭打ちになる。数十エージェントを何日も回す運用が現実になった結果、委譲ループやサーチループが暴走したときの被害が無視できなくなった、ということだろう。
実務者としてどう受け取るか
この事例から持ち帰るべきものを整理する。
まずコストの桁感だ。53万行の移行に16万5,000ドル、1ドル150円換算で約2,475万円。ソース1行あたり約0.31ドルである。人間のチームで同じ規模の書き換えを見積もったときの数字と比べれば、判断材料としては十分に具体的だろう。ただしこれはモデル料金だけであり、Sumnerは11日間ほぼずっと出力を読み、問題を見つけてはループを修正するプロンプトを書き続けていた。人間の関与がゼロになったわけではなく、関与する場所がコードからループに移動しただけだ。
次に適用範囲。前掲の4条件(並列分割可能、旧コードが仕様、客観的検証、キューの自己生成)が揃う仕事は、移行以外にもある。大規模なリファクタリング、型付けの導入、APIバージョンの一斉追従などは同じ構造を持つ。逆に、仕様が人の頭の中にしかない新規機能開発は、この型に当てはまらない。
最後に姿勢の話をする。ブログにあるもう一つの一文が、この種の作業の心構えをよく表している。最悪の場合でも、ブランチを削除してやり直すだけだ。移行作業が既存コードに手を入れず、独立したブランチで完結する限り、失敗のコストは投じたトークン代に限定される。この可逆性こそが、これまで着手できなかった規模の作業に手を出せるようになった本当の理由かもしれない。
参考リンク
How Anthropic runs large-scale code migrations with Claude Code (2026年7月16日)
Rewriting Bun in Rust (2026年7月8日)
Zig creator calls Bun's Claude Rust rewrite 'unreviewed slop' (2026年7月14日, The Register)
Claude Code CHANGELOG