作ったアプリに粗がある気がするからFable 5に相談した話

俺は aiterm-mcp という道具を作って npm に公開している。AIに1本の永続ターミナルを握らせて、そこから別のAIまで操作させる仕組みで、経緯は公開したときの記事とAI同士を会話させた記事に書いた。
これが最近、俺の開発環境の中心になってきた。いまの俺の環境は、Fable 5(Claudeの新しい最上位モデル)が全体の指揮をとって、Codex や Grok に作業を振る構成になっていて、その指示は全部 aiterm を通っている。英語で紹介ポストを出したら海外からも返事が付くようになって、起動前の環境検証を評価してくれる人や、「AIがAIを動かす部分より、ターミナル1本を維持できる方が効く」と言う人がいた。日本語圏にも、実際に使って設定の注意点を共有してくれる人が出てきた。
返事といっても数件なので、ほとんど誰も気づいていない道具だと思っていた。この記事を書くのに数字を確かめたら、npm のダウンロードが直近30日で1,416回あって、びっくりした。毎日誰かがどこかで入れているらしい。
そういう道具なので、これまでも何度も直してきた。ただ、直しても直しても、どこかに粗が残っているような感覚があった。どこが悪いのか自分でも言えないし、テストは全部通っていて、名指しできる不具合も無い。うっすら信用しきれない状態が続いていた。
それで、Fable 5 に相談してみることにした。伝えたのは「ブラッシュアップして」程度の一言で、どこを見ろとも何を直せとも言っていない。我ながら雑な相談だと思う。

戻ってきたら終わっていた
俺の環境には、数ヶ月かけて書き溜めた決まりごとがある。監査するときは複数の視点で並列に探させること。出てきた指摘は1件ずつ別のAIにぶつけて疑わせて、生き残ったものだけ採用すること。コードを触る前に、テストを先に用意して自動で回しておくこと。直したら実際に動かして確かめること。どれも過去に痛い目を見て決めたルールで、これまでの記事に書いてきたものだ。
Fable 5 はこの決まりごとを読んで、監査を自分で組み立てた。探す係のAIが7つの視点で並列に走り、指摘ごとに反証役が付き、取りこぼしを見る係まで含めて合計27体になった。外部のAI3系統(GPT-5.6・Grok 4.5・Composer)にもコードを見せて、そこから来た指摘もさらに6体の反証にかけていた。かかった時間は12分、処理量はトークン(AIが読み書きする量の単位)にして134万だった。
生の指摘は17件出て、反証を通ったのは7件だった。外部レビューから生き残った指摘もあわせて全部直して、道具はその日のうちに v0.11 から v0.12.1 になった。回帰テストは183本から197本に増えた。この日俺が書いたのはほぼ最初の一言で、残りは書き溜めたルールが動かしていた。
勘は当たっていた
粗は本当にあった。見つかったものから2つだけ紹介する。
ひとつ目は、危険なコマンドを止める検査がすり抜けられていたことだ。aiterm には rm -rf /(ディスクの中身を全部消すコマンド)のような危険なコマンドを送ろうとすると確認を要求する仕組みがある。この照合パターンが -- という正規の記法を想定していなくて、rm -rf -- / とハイフン2つを挟んだ書き方だと検査を通り抜けていた。直すのは1行で済んだ。
ふたつ目は、後始末の漏れだ。AIの返事を待っている最中にプロセスが強制終了されると、「いま待機中」という目印のファイルが残る。この目印を掃除するコードがどこにも無くて、目印が残ると、同じ端末に対する次の待機がずっと拒否され続ける。これは Codex と Claude の監査が、お互いを知らないまま同じ箇所を指摘してきた。
反証役も仕事をしていた。捨てられた10件は、もっともらしい間違いだった。たとえば「管理ファイルの書き込みが中断されると残骸が管理不能になる」という指摘があって、コードを読み直すと、壊れた管理ファイルがあっても普通のターミナルとして操作を続けられる逃げ道がもともとあり、問題の起きる時間窓もマイクロ秒単位だった。鵜呑みにして対策コードを足していたら、無駄に複雑になるところだった。
この「疑ってかかる係を別に置く」工程には、今年4月の論文にちょうど傍証がある。80体を超えるAIエージェントが、OpenSSL に存在しない脆弱性を全会一致で本物と認定してしまった事例が報告されていて、AIの指摘は数を集めても正しさの保証にならないことが分かっている。うちの「17件を7件に削る」は、その小さい版だ。
27体の誰も見つけられなかったバグ
一番厄介なバグは、この監査では見つからなかった。
aiterm は「AIがしゃべり終わったか」を、画面の出力が0.5秒間増えなくなったことで判定している。この判定には時間の隙間があって、出力の量を測る瞬間と、画面の状態を確かめる瞬間が少しずれている。その隙間に出力が届いたとき、終わったこと自体は正しく捕まえていて、「どうやって終わったか」の記録だけが実際と食い違うことがあった。
手元のMacだとテストは全部通る。これを見つけたのは GitHub の CI(コードを更新するたびに複数のOSで自動テストを回す仕組み)だった。CI の macOS 実行機はかなり遅い。問題のテストは、待ち時間0.6秒に対して判定に使う静止時間が0.5秒で、余裕が100ミリ秒しかない。遅い実行機ではこの余裕が足りなくなって、macOS のジョブが毎回確実に失敗した。ここで初めてバグの存在が分かった。

面白いのは、監査も惜しいところまで行っていたことだ。取りこぼしを見る係は「時間に依存する危ういテスト」という問題の種類には気づいていて、実際に別のテストを指摘していた。そちらは余裕が5倍あったので「問題なし」と判定されて、これは正解だった。本物の方は指摘されないままだった。コードを読むだけだと、余裕1.2倍のテストも5倍のテストも、同じ「通っているテスト」に見えるらしい。
ちなみにこのCIは、監査が走るより前から動いていた。コードを触る前に複数OSで自動テストを回せるようにしておくと昔の失敗から決めてあって、今回もその通りにした。
動かして初めて出たバグ
机上の監査とは別に2件、同じ日に回していた「実際に動かす確認」で出たバグがある。
Grok の画面ログを「全部読む」モードで取ったら、73,176文字が4行に詰まって返ってきた。画面を描き直すタイプのツールの出力には改行がほとんど入らない。aiterm の折りたたみ機能は「行数が60行を超えたら」で発動する設計だったので、この巨大な4行は折りたたまれないまま通り、Claude が一度に受け取れる上限を超えて、読み込み自体が失敗した。コードの上では折りたたみ機能はちゃんとあるように見える。1行の長さが問題になるケースは、実物の Grok を動かすまで、27体の誰からも出てこなかった。
もうひとつは、長い返事の取りこぼしだ。AIの返事が長いとき、aiterm は画面の最後の一画面分(約24行)だけを返す。40行の返事だと18行目から後ろだけが取れて、前半を取り戻す手段が無かった。これは返事の全文をAI側の会話記録から回収する機能を新しく作って解決した。
この2件は、コードを読むだけでは出てこなかった。実際に動かして、実物のデータが流れて、初めて出てきた。
誰が何を見つけたか
結果を並べるとこうなる。

雑な一言でここまで回ったのは、数ヶ月かけて書き溜めた決まりごとを Fable 5 が読んで動いたからだ。名指しできなかった「粗」は7件の実体になって、一日で消えた。反証の工程は、危ない思い込み10件を実装前に止めてくれた。そして一番厄介なバグは、CIを先に張っておく・直したら動かして確かめる、という昔の自分が決めておいたルールが見つけた。
見つかったバグは、この3つの間で1件も重なっていない。ふーむ。これは何を意味するんだろうな。