お疲れ様です!IT業界で働くアライグマです!
個人開発している開発関連ニュースのキュレーションサービス「DevPick」では、AIエージェントがローカルでバックエンド、フロントエンド、実ブラウザのテストを実行します。検証を増やすほど、異常のない成功ログが会話のコンテキストを占めるようになったため、ログの見せ方を作り直しました。
成功ログがAIの作業領域を占めていた
DevPickでは、Pythonのテストにpytest、フロントエンドのテストにVitest、実際のブラウザ操作の確認にPlaywrightを使っています。これにNext.jsのビルド確認も加わります。AIエージェントへ実装を任せると、変更後にこれらを実行し、結果を読んで次の判断へ進む流れになります。
問題は、テストが成功したときにも大量の途中経過が返ることでした。成功した検証で本当に知りたいのは、何件通ったか、警告や失敗がなかったか、コマンドが正常終了したかという短い結果です。しかし、実際の出力にはテストファイル名や進捗が何十行も並びます。異常がないほど、その大部分は次の判断には使いません。
AIエージェントにとって会話のコンテキストは、コード、仕様、調査結果を保持する作業領域でもあります。成功ログがそこを占めると、実装の前提やレビュー指摘に使える範囲が狭くなります。以前、Claude Codeで開発速度と品質を両立する運用ではレビューを別セッションへ分ける方法を書きましたが、今回は1つのセッションへ入る検証結果そのものを整理することにしました。
ただし、テストランナーの出力を一律に消す案は採りませんでした。失敗したときには、例外が最初に現れた場所や終了直前の状態が原因調査に必要です。成功時の冗長さを減らすことと、失敗時の証拠を残すことを、同じ設定で済ませようとしないのが出発点でした。
IT女子 アラ美完全ログを捨てず、一時ファイルへ隔離した
対応として、検証コマンドを包む小さなラッパーを追加しました。元のコマンドが出す標準出力と標準エラーをその場でAIへ返すのではなく、まず1つの完全ログへ保存します。その後、ログを読み取り、必要な部分だけを画面へ返します。
流れを単純化すると、次のようになります。
テストコマンドを実行する
├─ 全出力 → 一時的な完全ログへ保存
├─ 成功 → 要約だけを表示
└─ 失敗 → 診断範囲と完全ログの場所を表示
この形なら、普段は短い結果だけをAIエージェントへ渡しながら、要約だけでは原因を追えないときは完全ログへ戻れます。情報を削除したのではなく、常に会話へ流す情報と、必要なときだけ読む証拠を分けた形です。
完全ログは実行ごとに別のディレクトリへ置き、ディレクトリは所有者だけが読み書きできる権限、ファイルも所有者だけが読み書きできる権限で作ります。24時間を過ぎた完了済みログは次の実行時に削除しますが、並行して動いている検証のログは対象にしません。複数のAIエージェントや作業ツリーから同時にテストを走らせても、互いのログを消さないためです。
ラッパーが追加で記録するのは、元コマンドの出力だけです。環境変数やコマンド引数を診断用として勝手に書き足しません。一方で、テスト自体が認証情報を出力する場合まで安全に変えられるわけではないため、そのようなコマンドには使わないという境界も決めました。



成功時と失敗時で、表示する情報を分けた
成功時は、検証名、所要時間、正常終了したことに加え、テストランナーが出した件数の要約だけを表示します。pytestなら成功件数、Vitestならテストファイル数とテスト数、Playwrightなら総テスト数に当たる行です。該当する要約行がないビルドコマンドなどでは、出力の行数とバイト数を返します。
まずテストランナー自身に簡潔なreporterがある場合は、それを使います。ラッパーは各テストツールの表示を一から再実装せず、全体の出力量と終了状態を扱う役割に絞りました。既存ツールが持つ要約機能と、複数ツールに共通する制御を重ねる構成です。
失敗時は必要な情報が変わります。最初に失敗を示す行を見つけ、その周辺12行を表示します。さらにログの末尾40行を添え、セットアップの途中で最初の異常が出た場合と、最後にテストランナーがまとめた結果の両方を確認できるようにしました。それでも足りなければ、表示されたパスから完全ログを読めます。
大量出力への対策が、失敗時だけ外れてしまわないようにもしました。1行が極端に長い場合は1,000文字で表示を区切り、診断として画面へ返す全体にも16,000文字の上限を設けています。画面上では省略しても、完全ログには元の内容を残します。
以前のClaude Codeのトークン消費を抑える記事では、読み込むファイルやセッションを絞る方法が中心でした。今回の変更で対象にしたのは、必要な検証を減らさず、その結果の渡し方だけを絞る部分です。テストを省くのではなく、成功と失敗で情報価値が違うことを出力へ反映しました。



終了コードとシグナルを壊さない仕組みにした
ログを短く表示できても、ラッパーが検証結果を変えてしまえば意味がありません。AIエージェントは、画面に出た文章だけでなくコマンドの終了状態も見て、次へ進めるかを判断します。失敗したテストをラッパーが正常終了として返せば、未検証の変更を完成扱いする原因になります。
そこで、元コマンドが終了コード37を返したなら、ラッパーも37を返すようにしました。特定の1や2へ丸めません。元コマンドが終了コード0で完了した場合だけ成功として扱い、要約に成功らしい文字が含まれているかどうかで成否を決めない設計です。
中断も同じです。AIエージェント側から終了要求を受けた場合は、実行中の子プロセスへ同じシグナルを送り、ラッパー自身も同じシグナルで終了します。子プロセスだけを残したり、シグナルによる中断を通常のエラーコードへ置き換えたりしません。検証を包んでも、外側から観測できる終了の意味は変えないようにしました。
さらに、ログ用ディレクトリを安全に作れない、期限切れログを確認できない、失敗診断を生成できないといった場合は、検証済みとして扱わず非ゼロで終了します。安全でないシンボリックリンクがログ置き場にある場合も、元コマンドを実行する前に止めます。ログ整理の補助機能が失敗したときに、何も確認できていない状態を成功へ倒さないためです。
完全な出力をその場で見る必要がある場合には、verboseモードでラッパーの捕捉処理自体を迂回できます。この経路でも元コマンドの終了コードはそのまま返ります。普段の短い表示と、調査時の生の出力を切り替えても、検証の成否だけは一貫して保つようにしました。



実測して、ローカル検証だけへ適用した
導入時には、代表的な3種類の検証で完全ログと通常表示の行数を比べました。ラッパー自身のpytest統合テスト10件では、完全ログ12行・1,008バイトに対して通常表示は3行でした。Vitestのテスト4件では62行・2,339バイトが4行に、Playwrightで25件を列挙した結果では31行・3,881バイトが3行になりました。
ここで見たかったのは、ただ行数が減ったかだけではありません。成功件数や警告件数が短い表示に残ること、失敗時には最初の異常と末尾が出ること、任意の終了コードとシグナルが保たれることまで統合テストにしました。5,000行の成功出力を与えても途中経過を表示しないケースや、10万文字の1行を含む失敗でも診断表示が膨らみすぎないケースも固定しています。
一方、この仕組みはAIエージェントが手元で実行する検証だけに使い、CIのログには適用しませんでした。CIは、人が後から障害を解析する記録でもあります。AIの会話へ毎回流す情報量を減らす目的と、共有された実行履歴を人が確認する目的は同じではないためです。
適用範囲をローカル検証に絞ったことで、テストランナーやCIの設定全体を変更せずに済みました。pytest、Vitest、Playwright、ビルドの呼び出し方へ共通のラッパーを足し、AIエージェント向けの作業ルールからその経路を使います。誰が読むログなのかに合わせて、表示方法を入口で分けた形です。
今回の実測から、成功ログは数行でも検証結果を伝えられる一方、失敗の診断には別の設計が必要だと確認できました。情報量を減らすこと自体を目標にせず、次の判断に必要な情報が残ったかを基準にしています。



まとめ
DevPickのローカル検証では、テストが増えるほど成功時の途中経過がAIエージェントのコンテキストを占めていました。そこで、元コマンドの全出力は一時ログへ保存し、成功時は件数などの要約だけ、失敗時は最初の異常・ログ末尾・完全ログの場所を表示するラッパーを追加しました。
出力を短くしても、終了コードやシグナルは元コマンドのまま保ちます。ログを安全に扱えない場合は成功へ進めず、完全な出力が必要なら捕捉処理を迂回できます。pytest、Vitest、Playwrightで表示行数と診断内容を実測し、用途が異なるCIには広げませんでした。
今の私の結論は、AIエージェント向けのログ最適化では「何を消すか」より「どの状況で何を見せるか」を先に決めることです。成功の証明は短く、失敗の証拠は戻れる形で残し、検証結果そのものは変えない。この3点を分けることで、品質確認を減らさずに作業領域を守れるようになりました。












