バグの診断
難しいバグのための規律。明示的に正当化される場合を除き、フェーズをスキップしないでください。
コードベースを探索するときは、CONTEXT.md(存在する場合)を読んで関連モジュールの明確なメンタルモデルを構築し、触れている領域のADRを確認してください。
秘匿化
このスキルでは、コマンド、出力、キャプチャされた成果物を表示します。まずすべての機密情報を秘匿化してください: その場所に<REDACTED>と書きます。環境変数に対してループを構築し、認証情報が表示されるものではなく環境に残るようにします。キャプチャされた成果物には認証ヘッダーが含まれています: シグナルを含む行だけを引用してください。
秘匿化した出力だけではバグの診断に不十分な場合は、その旨を伝えてユーザーに尋ねてください。
フェーズ1: フィードバックループの構築
これがスキルの中核です。 それ以外は機械的な作業です。バグに対してタイトな合格/不合格シグナル(_この_バグで赤くなるもの)があれば、原因は見つかります。二分探索、仮説検証、計装はすべてそれを消費するだけです。タイトなシグナルがなければ、コードをいくら凝視しても助けにはなりません。
ここに不釣り合いなほど労力を費やしてください。積極的に。創造的に。諦めることを拒否してください。
構築方法(おおよそこの順序で)
- 失敗するテスト - バグに到達する任意のシームで: ユニット、インテグレーション、E2E。
- Curl / HTTPスクリプト - 実行中の開発サーバーに対して。
- CLI呼び出し - フィクスチャ入力を使用し、stdoutを既知の良いスナップショットと比較。
- ヘッドレスブラウザスクリプト (Playwright / Puppeteer) - UIを操作し、DOM/コンソール/ネットワークでアサーションを行う。
- キャプチャしたトレースのリプレイ - 実際のネットワークリクエスト/ペイロード/イベントログをディスクに保存し、コードパスを分離してリプレイ。
- 廃棄可能なハーネス - バグコードパスを単一の関数呼び出しで実行する最小限のシステム(1つのサービス、モックされた依存関係)をスピンアップ。
- プロパティ/ファズループ - バグが「時々間違った出力」の場合、1000個のランダム入力を実行し、障害モードを探す。
- 二分探索ハーネス - バグが2つの既知の状態(コミット、データセット、バージョン)の間に現れた場合、「状態Xで起動、チェック、繰り返し」を自動化して
git bisect runで実行できるようにする。 - 差分ループ - 同じ入力を旧バージョンと新バージョン(または2つの設定)で実行し、出力を比較。
- HITL bashスクリプト - 最後の手段。人間がクリックする必要がある場合、
scripts/hitl-loop.template.shを使って彼らを_ドライブ_し、ループが構造化されたままにする。キャプチャされた出力があなたにフィードバックされる。
適切なフィードバックループを構築すれば、バグは90%修正されたも同然です。
ループをタイトにする
ループをプロダクトとして扱ってください。ループが_一つ_できたら、タイトにしてください:
- より速くできるか?(セットアップをキャッシュ、無関係な初期化をスキップ、テスト範囲を絞る。)
- シグナルをよりシャープにできるか?(「クラッシュしなかった」ではなく、特定の症状に対してアサーションする。)
- より決定論的にできるか?(時間を固定、RNGをシード、ファイルシステムを分離、ネットワークをフリーズ。)
30秒でフレークするループはループがないのと大差ありません。2秒で決定論的なループはタイトであり、デバッグのスーパーパワーです。
非決定論的なバグ
目標は完全な再現ではなく、より高い再現率です。トリガーを100回ループし、並列化し、ストレスをかけ、タイミングウィンドウを狭め、スリープを挿入します。50%フレークするバグはデバッグ可能ですが、1%はそうではありません。デバッグ可能になるまで再現率を上げ続けてください。
どうしてもループを構築できない場合
止めて明示的にその旨を伝えてください。試したことをリストアップしてください。ユーザーに以下を依頼してください: (a) バグを再現する環境へのアクセス、(b) 秘匿化されたキャプチャ成果物(HARファイル、ログダンプ、コアダンプ、タイムスタンプ付きスクリーンレコーディング)、または (c) 一時的な本番計装を追加する許可。ループなしで仮説を立てる前進はしないでください。
完了基準: 赤くなるタイトなループ
ループがタイトで赤対応可能になったらフェーズ1は完了です: 1つのコマンド(スクリプトパス、テスト呼び出し、curl)を名前指定でき、それを少なくとも1回は実行済み(呼び出しとその出力を秘匿化して表示)であり、以下を満たしている:
- 赤対応: 実際のバグコードパスを駆動し、ユーザーの正確な症状をアサーションする。つまり、このバグで赤くなり、修正されれば緑になる。「エラーなしで実行」ではない。_この特定のバグ_をキャッチできなければならない。
- 決定論的: 毎回同じ判定(フレークするバグ: 上記に従い、固定された高い再現率)。
- 高速: 数分ではなく、数秒。
- エージェント実行可能: 無人で実行できる。人間がループに入るのは
scripts/hitl-loop.template.sh経由のみ。
もしこのコマンドが存在する前にコードを読んで理論を構築している自分に気づいたら、止めてください: 仮説に直接飛びつくことは、このスキルが防ぐまさにその失敗です。 赤対応コマンドがなければ、フェーズ2には進みません。
フェーズ2: 再現 + 最小化
ループを実行します。バグが現れて赤くなるのを見ます。
確認:
- ループがユーザーが説明した障害モードを生成している。偶然近くにある別の障害ではない。間違ったバグ = 間違った修正。
- 障害が複数回の実行で再現可能(または、非決定論的なバグの場合は、デバッグに十分な高い率で再現可能)。
- 正確な症状(エラーメッセージ、間違った出力、遅いタイミング)をキャプチャし、後のフェーズで修正が実際に対処していることを検証できるようにした。
最小化
赤くなったら、それでも赤くなる最小のシナリオに再現を縮小します。入力、呼び出し元、設定、データ、手順を一度に一つずつ削除し、削除のたびにループを再実行し、障害にとって耐荷重なものだけを残します。
なぜわざわざするのか: 最小の再現はフェーズ3での仮説空間を縮小し(疑うべき可動部品が少なくなる)、フェーズ5でクリーンな回帰テストになります。
残っているすべての要素が耐荷重になったら完了です: そのうちの一つを削除するとループが緑になります。
再現かつ最小化を完了するまでは先に進まないでください。
フェーズ3: 仮説の立案
仮説をテストする前に、3~5個のランク付けされた仮説を生成します。単一の仮説生成は最初の妥当なアイデアに固執させます。
各仮説は反証可能でなければなりません: それが行う予測を述べてください。
形式: 「が原因なら、<Yを変更>するとバグが消える / <Zを変更>すると悪化する」
予測を述べられない場合、その仮説は雰囲気です: 捨てるか、シャープにしてください。
テストする前にランク付けされたリストをユーザーに表示してください。 ユーザーはしばしばドメイン知識を持っており、すぐにランクを変更します(「ちょうど#3に変更をデプロイしたところだ」)か、すでに除外した仮説を知っています。安価なチェックポイントであり、大きな時間節約になります。ユーザーがAFKの場合は、あなたのランク付けで進めてください。ブロックしないでください。
フェーズ4: 計装
各プローブはフェーズ3からの特定の予測に対応しなければなりません。一度に一つの変数だけを変更してください。
ツールの優先順位:
- デバッガー / REPL検査 - 環境がサポートしている場合。一つのブレークポイントが10個のログに勝ります。
- ターゲットを絞ったログ - 仮説を区別する境界で。
- 「すべてをログに記録してgrep」はしない。
すべてのデバッグログに一意のプレフィックスをタグ付けしてください(例: [DEBUG-a4f2])。最後のクリーンアップが単一のgrepで済むようになります。タグなしのログは残り、タグ付きのログは削除されます。
パフォーマンスブランチ。 パフォーマンス劣化の場合、ログは通常間違っています。代わりに: ベースライン測定(タイミングハーネス、performance.now()、プロファイラー、クエリプラン)を確立し、その後二分探索します。最初に測定し、次に修正します。
フェーズ5: 修正 + 回帰テスト
修正の前に回帰テストを書きますが、正しいシームがある場合に限ります。
正しいシームとは、テストが実際のバグパターンをそれが発生する呼び出しサイトで実行するものです。利用可能な唯一のシームが浅すぎる場合(バグが複数の呼び出し元を必要とするのに単一呼び出し元のテスト、バグを引き起こしたチェーンを再現できないユニットテスト)、そこに回帰テストを書いても誤った安心感を与えます。
正しいシームが存在しない場合、それ自体が発見です。 それを記録してください。コードベースのアーキテクチャがバグの固定を妨げています。次のフェーズのためにフラグを立ててください。
正しいシームが存在する場合:
- 最小化された再現をそのシームで失敗するテストに変換します。
- それが失敗するのを見ます。
- 修正を適用します。
- それが成功するのを見ます。
- フェーズ1のフィードバックループを元の(最小化されていない)シナリオに対して再実行します。
フェーズ6: クリーンアップ
完了と宣言する前に必要:
- 元の再現が再現しなくなった(フェーズ1のループを再実行)
- 回帰テストが成功する(またはシームがないことが文書化されている)
- すべての
[DEBUG-...]計装が削除された(プレフィックスをgrep) - 使い捨てプロトタイプが削除された(または明確にマークされたデバッグ場所に移動された)
- 正しかった仮説がコミット/PRメッセージに記載され、次のデバッガーが学べるようにする