diagnosing-bugs について
開発者やコーディングエージェント向けの難しいバグの調査手順です。症状を検出する高速で決定的なチェックを先に作り、再現、仮説検証、修正、後片付けへ進みます。
主な機能
- 01症状に沿ったフィードバックループの構築
- 02障害の再現と縮小
- 03反証可能な仮説の順位付け
- 04対象を絞った調査
- 05回帰テストの追加と後片付け
活用シーン
- 01断続的に起きるバグの調査
- 02報告された症状の再現テスト化
- 03診断記録を保ちながら原因を追跡
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ utarn/ engineer-skills --skill diagnosing-bugsインストール後、Claude Code で/diagnosing-bugsを入力して読み込まれたか確認します。
同梱ファイルagents/openai.yamlscripts/hitl-loop.template.sh
詳細
バグの診断
難しいバグに取り組むための規律です。明確な理由がある場合に限り、フェーズを省略してください。
コードベースを調査する際は、関連するモジュールについて明確な全体像を把握するために、CONTEXT.md(存在する場合)を読み、作業対象の領域にある ADR も確認してください。
秘密情報のマスキング
このスキルでは、コマンド、出力、取得したアーティファクトを提示します。まず、すべての秘密情報をマスキングしてください。その箇所には <REDACTED> と記載します。環境変数を使ってビルドループを組み、認証情報が提示内容ではなく環境内に留まるようにしてください。取得したアーティファクトには認証ヘッダーが含まれます。シグナルを示す行だけを引用してください。
マスキングした出力だけではバグを診断できない場合は、その旨を伝えてユーザーに尋ねてください。
フェーズ 1 — フィードバックループを構築する
これこそがこのスキルの本質です。 それ以外はすべて機械的な作業です。このバグで失敗する厳密な合否シグナルがあれば、原因を突き止められます。二分探索、仮説検証、計測はいずれも、そのシグナルを活用するだけです。シグナルがなければ、コードをいくら見つめても解決できません。
ここに、労力の大部分を注いでください。果敢に取り組み、創意工夫を凝らし、決して諦めないでください。
フィードバックループの構築方法 — おおむね次の順に試してください
- バグに到達する境界のどこかで、失敗するテストを作成します。ユニットテスト、統合テスト、E2E テストなどが該当します。
- 起動中の開発サーバーに対して、Curl または HTTP スクリプトを実行します。
- フィクスチャ入力を使って CLI を実行し、標準出力を既知の正常なスナップショットと比較します。
- ヘッドレスブラウザースクリプト(Playwright / Puppeteer)で UI を操作し、DOM/コンソール/ネットワークを検証します。
- 取得済みのトレースを再生します。実際のネットワークリクエスト、ペイロード、イベントログをディスクに保存し、コードパスに分離して再生します。
- 使い捨てのハーネスを用意します。システムの最小限の構成(単一サービス、依存関係のモック)を起動し、単一の関数呼び出しでバグのコードパスを実行します。
- プロパティテスト/ファジングのループを作ります。「出力がときどき間違う」バグなら、ランダムな入力を 1000 件実行し、失敗のパターンを探します。
- 二分探索用のハーネスを作ります。バグが既知の 2 つの状態(コミット、データセット、バージョン)の間で発生した場合は、「状態 X で起動して確認する」を自動化し、
git bisect runで実行できるようにします。 - 差分ループを作ります。同じ入力を旧バージョンと新バージョン(または 2 つの設定)に渡し、出力を比較します。
- 最終手段として、HITL bash スクリプトを使います。人間がクリックする必要がある場合は、
scripts/hitl-loop.template.shで_人間自身を_操作し、ループを構造化したままにします。取得した出力を次の調査に活用します。
適切なフィードバックループを構築すれば、バグは 90% 解決したも同然です。
ループを改善する
ループをプロダクトとして捉えてください。_ループを_作れたら、さらに改善します。
- もっと速くできますか?(セットアップをキャッシュする、無関係な初期化を省く、テスト範囲を絞る)
- シグナルをもっと明確にできますか?(「クラッシュしなかった」ではなく、具体的な症状を検証する)
- もっと決定的にできますか?(時刻を固定する、乱数生成器にシードを指定する、ファイルシステムを分離する、ネットワークを固定する)
不安定な 30 秒のループは、ループがないのとほとんど変わりません。決定的な 2 秒のループは、デバッグにおける強力な武器です。
非決定的なバグ
目標は、完全に再現できることではなく、再現率を上げることです。トリガーを 100 回ループさせ、並列化し、負荷をかけ、タイミングの範囲を狭め、スリープを注入します。50% の確率で発生するバグならデバッグできますが、1% では困難です。デバッグできるようになるまで、再現率を上げ続けてください。
どうしてもループを構築できない場合
作業を止め、その旨を明確に伝えてください。試したことを列挙し、ユーザーに次のいずれかを依頼してください。(a)再現環境へのアクセス、(b)マスキング済みの取得アーティファクト(HAR ファイル、ログダンプ、コアダンプ、タイムスタンプ付きの画面録画)、または(c)本番環境に一時的な計測を追加する許可。ループがないまま仮説を立てて先に進んではいけません。
完了基準 — 失敗するタイトなループ
フェーズ 1 は、ループがタイトで失敗を検出できる状態になったら完了です。1 つのコマンド(スクリプトのパス、テストの実行コマンド、curl など)を挙げられ、そのコマンドを少なくとも 1 回はすでに実行済み(実行コマンドと出力を伏せ字にして提示)で、次の条件を満たしている必要があります。
- 失敗を検出できる — 実際のバグのコードパスを通り、ユーザーが示した正確な症状をアサートするため、このバグがある状態では失敗し、修正後は成功します。「エラーなしで実行できる」だけでは不十分です。_この特定のバグを検出できる_必要があります。
- 決定的 — 実行するたびに同じ判定になります(不安定なバグの場合は、上記のとおり、再現率を高く固定します)。
- 高速 — 数秒で終わり、数分はかかりません。
- エージェントが実行可能 — 人の介入なしで実行でき、人間が介入する場合は
scripts/hitl-loop.template.sh経由に限られます。
このコマンドが存在する前に、仮説を立てるためにコードを読み始めていることに気づいたら、そこで止めてください。仮説に直行することこそ、このスキルが防ぐ失敗です。 失敗を検出できるコマンドがなければ、フェーズ 2 には進めません。
フェーズ 2 — 再現と最小化
ループを実行します。失敗する様子を確認します。バグが現れます。
以下を確認してください。
- ループが、別の近くにある失敗ではなく、ユーザーが説明した失敗のモードを発生させます。バグを取り違えれば、修正も間違います。
- 複数回実行しても失敗が再現されます(非決定的なバグの場合は、デバッグに十分な高い確率で再現されます)。
- 症状を正確に記録してあります(エラーメッセージ、誤った出力、遅い実行時間など)。後のフェーズで、修正が実際にその症状に対処しているかを検証できます。
最小化
失敗を確認したら、失敗が続く最小のシナリオまで再現手順を縮小します。入力、呼び出し元、設定、データ、手順を1 つずつ削り、削るたびにループを再実行します。失敗に不可欠なものだけを残してください。
最小化する理由:最小の再現手順にすると、フェーズ 3 で考慮すべき仮説の範囲が狭まり(疑う要素が減り)、フェーズ 5 で明確な回帰テストとして使えるようになります。
残ったすべての要素が不可欠になったら完了です。いずれか 1 つを取り除くと、ループが成功します。
再現と最小化の両方が終わるまで先に進まないでください。
フェーズ 3 — 仮説を立てる
検証する前に、優先順位を付けた 3〜5 個の仮説を立てます。仮説を 1 つだけ立てると、最初のもっともらしい考えに引きずられます。
各仮説は反証可能でなければなりません。そこから導かれる予測を述べてください。
形式:「 が原因なら、 を変更するとバグが消えます/ を変更すると悪化します。」
予測を述べられない場合、その仮説は単なる思いつきです。却下するか、明確にしてください。
検証する前に、優先順位を付けた一覧をユーザーに見せてください。 ユーザーには、その順位をすぐに変更できるドメイン知識があることがよくあります(「#3 に変更をデプロイしたばかりです」など)。すでに否定した仮説を知っている場合もあります。手間の小さい確認で、大幅に時間を節約できます。ユーザーの返答を待つ必要はありません。ユーザーが離席中なら、自分の順位付けに従って進めてください。
フェーズ 4 — 計測
各プローブは、フェーズ 3 で立てた具体的な予測に対応させます。一度に変更する変数は 1 つだけです。
ツールの優先順位:
- デバッガー/REPL による調査 — 環境が対応している場合。ブレークポイント 1 つはログ 10 個に勝ります。
- 対象を絞ったログ — 仮説を見分けられる境界に追加します。
- 「すべてをログに出して grep」は決して行わないでください。
デバッグログにはすべて、[DEBUG-a4f2] のような一意のプレフィックスを付けてください。最後に grep を 1 回行うだけで削除できます。プレフィックスのないログは残り、付けたログは消せます。
パフォーマンス関連の場合。 パフォーマンスのリグレッションでは、ログは通常適切ではありません。代わりに、ベースラインの計測(計測ハーネス、performance.now()、プロファイラー、クエリプランなど)を行ってから、二分探索します。先に計測し、修正はその後です。
フェーズ 5 — 修正+回帰テスト
修正する前に回帰テストを書きます。ただし、適切なテストの接点がある場合に限ります。
適切なテストの接点とは、呼び出し元で実際に起きている本物のバグパターンをテストで検証できる場所です。利用できる接点が浅すぎる場合(複数の呼び出し元が必要なバグに対して呼び出し元が1つだけのテスト、バグを引き起こした呼び出しの連鎖を再現できない単体テストなど)、そこで回帰テストをしても誤った安心感につながります。
適切なテストの接点がない場合、それ自体が調査結果です。 その旨を記録してください。コードベースのアーキテクチャがバグをテストで封じ込めることを妨げています。次のフェーズに向けて報告してください。
適切なテストの接点がある場合:
- 最小化した再現ケースを、その接点で失敗するテストにします。
- 失敗することを確認します。
- 修正を適用します。
- 成功することを確認します。
- 元の(最小化していない)シナリオに対して、フェーズ 1 のフィードバックループを再実行します。
フェーズ 6 — クリーンアップ+事後分析
完了を宣言する前に、以下を必ず行ってください。
- 元の再現ケースでバグが再現しなくなった(フェーズ 1 のループを再実行)
- 回帰テストが成功する(または適切なテストの接点がないことを記録済み)
-
[DEBUG-...]の計測コードをすべて削除済み(プレフィックスをgrepで検索) - 使い捨てのプロトタイプを削除済み(または、デバッグ用と明記した場所に移動済み)
- 正しいと判明した仮説をコミット/PR のメッセージに記載済み(次にデバッグする人が学べるように)
次に、何があればこのバグを防げたかを考えてください。 答えにアーキテクチャの変更(適切なテストの接点がない、呼び出し元が複雑に絡み合っている、隠れた結合など)が含まれる場合は、具体的な内容を添えて /improve-codebase-architecture スキルに引き継いでください。推奨事項を示すのは、修正が済んでからにしてください。着手時よりも多くの情報が得られているためです。