# コーディングエージェントが CLI の失敗から必要とするもの

コーディングエージェントは CLI を実行して、2 つのものを読み取ります。終了コードと標準出力です。よく使われる JavaScript の CLI 12 個について、すべて同じ方法で失敗の仕方を測りました。存在しないオプションを拒否するのは 10 個ですが、その間違いに本当の失敗とは別の終了コードを返すのは 2 個だけです。JSON モードがある 7 個のうち、実行が失敗したときに解析できる JSON を出すのは 6 個、ツールがそもそも起動できないときにも出すのは 1 個でした。もう一度試せば直るかどうかを示す出力は、どれにもありません。スクリプトと生の結果は [`bench/cli-failures`](https://github.com/debuffjp/debuff/tree/main/bench/cli-failures) にあります。

| ツール | バージョン | 未知のオプション | 使い方の誤り vs 本当の失敗（終了コード） | 失敗時の JSON | 起動できないときの JSON |
| --- | --- | --- | --- | --- | --- |
| ESLint | 10.13.0 | 拒否 | 2 vs 1 | JSON | 標準出力は空 |
| webpack | 5.112.2 | 拒否 | 2 vs 1 | JSON | 標準出力は空 |
| npm | 12.2.0 | 拒否 | 1 vs 1 | JSON | コード付きの JSON |
| Jest | 30.5.2 | 拒否 | 1 vs 1 | JSON | 標準出力は空 |
| Biome | 2.6.3 | 拒否 | 1 vs 1 | JSON | 標準出力は空 |
| oxlint | 1.90.0 | 拒否 | 1 vs 1 | JSON | テキスト |
| Vitest | 5.0.5 | 拒否、スタックトレース付き | 1 vs 1 | ファイルに書き出し | 標準出力は空 |
| TypeScript (tsc) | 7.0.2 | 拒否 | 1 vs 1 | JSON モードなし | JSON モードなし |
| Vite | 8.3.5 | 拒否、スタックトレース付き | 1 vs 1 | JSON モードなし | JSON モードなし |
| esbuild | 0.28.2 | 拒否、スタックトレース付き（Windows） | 1 vs 1 | JSON モードなし | JSON モードなし |
| Prettier | 3.9.10 | 警告付きで無視 | 0 vs 1 | JSON モードなし | JSON モードなし |
| Rollup | 4.65.1 | 警告付きで無視 | 0 vs 1 | JSON モードなし | JSON モードなし |

Windows 11、Node 24.15.0、どのツールも 2026-10-28 時点の最新リリースです。「起動できない」は、渡した設定ファイルが存在しない場合です（npm は存在しないパッケージ）。

## エージェントにとってなぜ重要か {#why-an-agent-cares}

人は失敗を読めば、それがどんな種類かわかります。エージェントは終了コードと標準出力から判断するしかなく、判断は次の 4 つのどれかです。

1. うまくいった。そのまま続ける。
2. 確かめた対象が失敗した（型エラー、テストの失敗）。レポートを読んでコードを直す。
3. コマンドそのものが間違っていた。コマンドを直す。
4. マシンかプロジェクトの準備ができていない。そこを直し、同じコマンドを再試行しない。

これを取り違えると高くつきます。打ち間違えたフラグをテストの失敗と読んだエージェントは、問題のないコードからバグを探し始めます。設定ファイルがないのに再試行するエージェントはループします。以下の計測のほとんどは、文章を解析しなくてもエージェントがこれらを見分けられるかどうかについてです。

## わかったこと {#what-we-found}

1. **ほとんどのツールは、存在しないオプションを拒否します。** 12 個中 10 個が `--frobnicate` で 0 以外の終了コードを返します。いちばん丁寧なのは ESLint で、`Invalid option '--frobnicate' - perhaps you meant '--format'?` と出します。Prettier と Rollup は警告を出したうえで実行を続けます。人には見えても、標準出力をパイプで読むエージェントは見落とすかもしれません。
2. **3 つは拒否をスタックトレースで返します。** Vitest と Vite はメッセージのあとに、それを投げた引数パーサーのスタックを表示します。esbuild 自身のエラーはきれいですが、Windows では Node のランチャーが上にスタックを足します。どれも間違いではありませんが、出力の先頭を読むエージェントは、問題より先にパーサーの内部を目にします。
3. **「打ち間違えた」と「コードが失敗した」を終了コードで区別するツールはまれです。** 使い方の誤りで 2、本当の失敗で 1 を返すのは ESLint と webpack だけでした。残りの 8 個では、`tsc --noEmit --frobnicate good.ts` と `tsc --noEmit bad.ts` はどちらも 1 で終わるので、終了コードだけでは不正なフラグが型エラーに見えます。
4. **JSON モードは失敗には対応していても、セットアップには対応していません。** 本当の理由で失敗したとき、JSON モード 7 個中 6 個は標準出力に解析できるドキュメントを 1 つ出します。Vitest は JSON レポートをファイルに書き、そのパスを表示します。理にかなった選択ですが、パイプ側はそれを知っておく必要があります。設定ファイルがなくてツールが起動できないとき、7 個中 5 個は標準出力に何も出さず、標準エラー出力にテキストのエラーを出します。oxlint は標準出力にテキストを出します。JSON で答えるのは npm だけで、エラーコード付きです（存在しないパッケージなら `E404`）。
5. **再試行すべきかどうかを示すツールはありません。** いちばん近いのは npm のコードです。`E404` と `ENOTFOUND` のようなネットワークの失敗は別のコードなので、npm のコードを知っているエージェントなら区別できます。その知識なしにエージェントが判断できるフィールドは、今回取得した出力のどれにもありませんでした。

## 4 つすべてに答える取り決め {#a-contract-that-answers-all-four}

debuff の CLI には、0.20 から失敗についての取り決めが 1 つあります。

| 終了コード | 意味 | すること |
| --- | --- | --- |
| 0 | 成功 | そのまま続ける |
| 1 | 実行が失敗を報告した（テスト、シナリオ、ファズの発見）、または予期しないエラー | レポートを読む |
| 2 | 先にプロジェクトかマシンを直す必要がある（ブラウザがない、設定が不正、dev フックがない、ポートが使用中） | `npx debuff doctor` を実行する。再試行しても変わらない |
| 3 | 入力が不正（不明なコマンドやオプション、存在しないフォルダ） | コマンドを直す |

`--json` を付けると、標準出力にはちょうど 1 つの JSON だけが出ます。実行できなかったコマンドは、結果の代わりに次を出します。

```json
{ "error": { "code": "browser_not_found", "message": "…", "fix": "Install Chrome or Edge, or set DEBUFF_BROWSER…", "transient": false } }
```

`code` は決まった一覧の中の値で、リリースをまたいで意味が変わりません。`transient: true` は、同じコマンドを再実行すれば成功する可能性があるという意味です。debuff の MCP ツールのエラーにも、同じ code と fix が付きます。

このベンチは debuff も、変更の前と後で計測しています。

| debuff | 未知のオプション | 使い方の誤り vs 本当の失敗（終了コード） | 失敗時の JSON | 起動できないときの JSON |
| --- | --- | --- | --- | --- |
| 0.19 | 何も言わずに無視 | 0 vs 1 | JSON ではなくテキスト | テキスト |
| 0.20 | 拒否 | 3 vs 1 | JSON | コード付きの JSON |

0.19 は上のツールの多くより悪い結果でした。未知のオプションを何も言わずに無視し、`--json` の実行が失敗すると JSON ではなく人向けのエラーを出していました。0.20 にベンチを走らせたことで、もう 1 つ穴が見つかりました。シナリオが 1 つもないプロジェクトは、`unexpected` として返っていたのです。0.21 からは専用のコード `nothing_to_run` が付きます。

## ツールの作者へ {#for-tool-authors}

エージェントが実行する CLI にお願いしたいこと。

1. 未知のオプションは拒否し、ESLint のようにいちばん近い正しいオプションを示す。
2. 使い方の誤りには専用の終了コードを使う。ESLint と webpack は 2 です。
3. JSON モードでは、ツールが起動できないときも含めて、標準出力を 1 つのドキュメントに保つ。ログは標準エラー出力へ。
4. エラーに変わらないコードを付け、再試行で直るかどうかを示す。
5. 利用者の間違いには、スタックトレースではなくメッセージを出す。

## この計測でわからないこと {#what-this-doesn-t-show}

- プラットフォームは 1 つ（Windows）、実行は 1 回です。esbuild のスタックトレースは Windows のランチャーによるもので、Linux と macOS では npm がネイティブのバイナリを直接リンクします。
- ツールごとに、代表的なコマンドを 1 つ選んでいます。別のサブコマンドではもっとよい振る舞いをするかもしれません。
- 起動できない状況は 1 種類だけ（設定ファイルがない）です。設定の中身が不正、依存関係がない、ネットワークが落ちている、といった場合は結果が違うかもしれません。
- 警告とスタックトレースのラベルは出力のパターンマッチによるものです。確かめられるように、各行に生の終了コードと出力を残しています。

自分で実行するか、ツールを追加して PR を送ってください。

```bash
node bench/cli-failures/run.mjs
```