バグの診断
難しいバグに対する規律。フェーズを省略するのは、明示的に正当化できる場合のみ。
コードベースを探索する際は、CONTEXT.md が存在すればそれを読み、関連モジュールの明確なメンタルモデルを得る。また、これから触る領域のADRを確認する。
秘匿情報のマスキング
このスキルでは、コマンド・出力・取得したアーティファクトを提示する。最初にすべての秘匿情報をマスキングすること: その場所に <REDACTED> と書く。ループは環境変数に対して構築し、認証情報が表示内容ではなく環境側に留まるようにする。取得したアーティファクトには認証ヘッダーが含まれることがあるので、シグナルとなる行だけを引用する。
マスキング後の出力だけでは診断に不十分な場合は、その旨を伝えてユーザーに確認する。
フェーズ1: フィードバックループを構築する
これがこのスキルの本質である。 それ以外はすべて機械的な作業にすぎない。この特定のバグでredになる、締まったpass/failシグナルさえあれば、必ず原因にたどり着く。二分探索・仮説検証・計装は、そのシグナルを消費するだけの存在である。それがなければ、どれだけコードを見つめても救われない。
ここに不釣り合いなほどの労力をかけること。貪欲に。創造的に。決してあきらめない。
構築の方法(おおむねこの順序で)
- バグに到達する任意のシームでの失敗するテスト: 単体、結合、e2e。
- 稼働中の開発サーバーに対するcurl / HTTPスクリプト。
- 既知の正しいスナップショットとstdoutを比較する、フィクスチャ入力を使ったCLI呼び出し。
- UIを操作しDOM/コンソール/ネットワークをアサーションするヘッドレスブラウザスクリプト(Playwright / Puppeteer)。
- キャプチャしたトレースの再生。 実際のネットワークリクエスト/ペイロード/イベントログをディスクに保存し、そのコード経路単体で再生する。
- 使い捨てのハーネス。 バグのあるコード経路を1回の関数呼び出しで行使できる、システムの最小限のサブセット(1サービス、依存はモック)を立ち上げる。
- プロパティ/ファジングループ。 バグが「時々おかしな出力になる」というものなら、ランダムな入力を1000個試し、失敗パターンを探す。
- 二分探索ハーネス。 バグが既知の2つの状態(コミット、データセット、バージョン)の間で発生した場合、「状態Xで起動→確認→繰り返し」を自動化し、
git bisect runできるようにする。 - 差分ループ。 同じ入力を旧バージョンと新バージョン(または2つの設定)の両方に通し、出力を比較する。
- HITL(Human-in-the-loop)bashスクリプト。 最後の手段。人間がクリックしなければならない場合、
${CLAUDE_SKILL_DIR}/scripts/hitl-loop.template.shでその人間を誘導し、ループを構造化されたものに保つ。取得した出力があなたにフィードバックされる。
正しいフィードバックループを構築すれば、バグは9割解決したようなものである。
ループを締める
ループを1つの製品として扱う。ループができたら、締める:
- もっと速くできないか?(セットアップのキャッシュ化、無関係な初期化のスキップ、テスト範囲の絞り込み)
- シグナルをもっと鋭くできないか?(「クラッシュしなかった」ではなく、具体的な症状をアサーションする)
- もっと決定的にできないか?(時刻を固定、乱数シードを固定、ファイルシステムを分離、ネットワークを固定する)
30秒かかる不安定なループは、ループなしとほぼ変わらない。2秒で決定的なループは締まっており、デバッグの超能力になる。
非決定的なバグ
目標はクリーンな再現ではなく、再現率を上げることである。トリガーを100回ループさせ、並列化し、負荷をかけ、タイミングの窓を狭め、sleepを注入する。50%の頻度で発生するバグはデバッグ可能だが、1%では不可能なので、デバッグ可能になるまで頻度を上げ続ける。
本当にループを構築できない場合
立ち止まり、その旨を明示的に伝える。試したことを列挙する。ユーザーに以下を依頼する: (a) 再現する環境へのアクセス、(b) マスキング済みの取得アーティファクト(HARファイル、ログダンプ、コアダンプ、タイムスタンプ付きの画面録画)、(c) 一時的な本番計装を追加する許可。ループなしで仮説を立てる段階に進まないこと。
完了基準: redになる締まったループ
フェーズ1が完了するのは、ループが締まっており、redになれる状態のとき: 1つのコマンド(スクリプトのパス、テスト呼び出し、curl)を名指しでき、それを少なくとも一度実行済みであり(その呼び出しと出力をマスキングして提示する)、かつ以下を満たす:
- redになれる: 実際のバグのコード経路を通り、ユーザーが述べた正確な症状をアサーションしている。これによりこのバグでredになり、直ればgreenになれる。「エラーなく動く」ではなく、この特定のバグを検知できること。
- 決定的: 実行するたびに同じ結果になる(不安定なバグの場合は、上記の通り再現率を固定して高くする)。
- 速い: 分単位ではなく秒単位。
- エージェントだけで実行可能: 無人で実行できる。人間が関わるのは
${CLAUDE_SKILL_DIR}/scripts/hitl-loop.template.sh経由のときのみ。
このコマンドが存在する前にコードを読んで理論を組み立て始めていることに気づいたら、止まること: 仮説にいきなり飛びつくのは、まさにこのスキルが防ごうとしている失敗である。 redになれるコマンドがなければ、フェーズ2には進まない。
フェーズ2: 再現と最小化
ループを実行する。バグが現れてredになるのを確認する。
確認すること:
- ループが生み出す失敗は、たまたま近くにある別の失敗ではなく、ユーザーが説明した失敗モードそのものである。バグを取り違えれば、修正も的外れになる。
- その失敗は複数回の実行にわたって再現する(非決定的なバグの場合は、デバッグ可能な高さの頻度で再現する)。
- 正確な症状(エラーメッセージ、誤った出力、遅いタイミング)をキャプチャしてあり、後のフェーズで修正が実際に効いたか検証できる。
最小化
redになったら、まだredになる最小のシナリオまで再現を縮小する。入力・呼び出し元・設定・データ・手順を1つずつ削り、削るたびにループを再実行し、失敗にとって不可欠な要素だけを残す。
なぜやるのか: 最小化された再現は、フェーズ3での仮説空間を縮小し(残る疑わしい可動部品が減る)、フェーズ5でのクリーンな回帰テストになる。
残っているすべての要素が不可欠になったら完了: そのうちどれか1つを取り除くと、ループがgreenになる状態。
再現と最小化の両方が終わるまで先に進まないこと。
フェーズ3: 仮説を立てる
どれか1つを検証する前に、3〜5個のランク付けされた仮説を生成する。単一の仮説だけを生成すると、最初に思いついたもっともらしいアイデアに固執してしまう。
各仮説は反証可能でなければならない: それが立てる予測を明記する。
形式: 「もしが原因なら、<Yを変えると>バグが消える/<Zを変えると>バグが悪化する。」
その予測を言葉にできないなら、その仮説は勘にすぎない。捨てるか、研ぎ澄ますこと。
検証する前に、ランク付けした一覧をユーザーに見せる。 ユーザーは即座に順位を入れ替えるドメイン知識を持っていることが多い(「#3には最近デプロイで変更を入れた」など)、あるいは既に除外済みの仮説を知っていることもある。安いチェックポイントで、大きな時間の節約になる。これをブロッキングにする必要はない。ユーザーが離席中なら、自分のランク付けのまま進める。
フェーズ4: 計装する
各プローブはフェーズ3の特定の予測に対応させる。一度に1つの変数だけを変える。
ツールの優先順位:
- 環境がサポートしていればデバッガ/REPLでの検査。1つのブレークポイントは10個のログに勝る。
- 仮説を区別できる境界での狙いを定めたログ。
- 「全部ログに出してgrepする」は絶対にしない。
すべてのデバッグログに一意のプレフィックスをタグ付けする(例: [DEBUG-a4f2])。最後の掃除が1回のgrepで済むようになる。タグ付けされていないログは生き残ってしまうが、タグ付けされたログは死ぬ(消せる)。
パフォーマンス分岐。 パフォーマンス劣化の場合、ログは大抵役に立たない。代わりに: ベースライン計測(計測ハーネス、performance.now()、プロファイラ、クエリプラン)を確立してから二分探索する。まず計測、修正はその後。
フェーズ5: 修正と回帰テスト
修正の前に回帰テストを書く。ただし正しいシームが存在する場合に限る。
正しいシームとは、そのバグの実際のパターンを、呼び出し箇所で発生する通りに行使できるテストのことである。利用可能なシームが浅すぎる場合(複数呼び出し元が必要なバグに対する単一呼び出し元のテスト、バグを引き起こした連鎖を再現できない単体テストなど)、そこに回帰テストを置いても偽の安心感しか得られない。
正しいシームが存在しないなら、それ自体が発見である。 それを書き留める。コードベースのアーキテクチャが、バグを封じ込めることを妨げている。これを次のフェーズのために記録する。
正しいシームが存在するなら:
- 最小化した再現を、そのシームでの失敗するテストに変える。
- 失敗するのを確認する。
- 修正を適用する。
- 成功するのを確認する。
- 元の(最小化していない)シナリオに対して、フェーズ1のフィードバックループを再実行する。
フェーズ6: 後片付け
完了を宣言する前に必須:
- 元の再現がもう再現しない(フェーズ1のループを再実行して確認)
- 回帰テストが通る(またはシームが存在しないことが文書化されている)
-
[DEBUG-...]の計装がすべて除去されている(プレフィックスをgrepして確認) - 使い捨てのプロトタイプが削除されている(または明確にマークされたデバッグ用の場所に移されている)
- 正しかった仮説がコミット/PRメッセージに記載されており、次にデバッグする人が学べるようになっている