diagnosing-bugs について
diagnosing-bugs は、複雑なソフトウェア障害を調査する開発者向けのデバッグスキルです。報告された症状を検出する再現可能なフィードバックループを先に作り、再現条件を絞って修正を検証します。
主な機能
- 01症状に特化したループの構築
- 02障害の再現と最小化
- 03反証可能な原因仮説の作成
- 04対象を絞ったプローブで仮説を検証
- 05回帰テストの追加と後片付け
活用シーン
- 01断続的な障害の調査
- 02再現困難なバグのテスト作成
- 03修正の検証と再発防止
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ maddec01/ mux-factory --skill diagnosing-bugsインストール後、Claude Code で/diagnosing-bugsを入力して読み込まれたか確認します。
同梱ファイルagents/openai.yamlscripts/hitl-loop.template.sh
詳細
バグの診断
難しいバグに取り組むための規律です。明確な理由がある場合に限り、フェーズを省略してください。
コードベースを調査する際は、関連モジュールの全体像を把握するために GLOSSARY.md(存在する場合)を読み、作業対象の領域に関する ADR も確認してください。
編集
このスキルでは、コマンド、出力、取得した成果物を提示します。まず、すべてのシークレットを必ず編集してください。該当箇所には <REDACTED> と記載します。環境変数を使ってビルドループを実行し、認証情報を表示内容ではなく環境内にとどめてください。取得した成果物には認証ヘッダーが含まれているため、問題の手がかりとなる行だけを引用してください。
編集後の出力だけではバグを診断できない場合は、その旨を伝えてユーザーに確認を求めてください。
フェーズ 1:フィードバックループを構築する
これがこのスキルの要です。 バグに対する確実な合否判定シグナル(そのバグがある場合に失敗するもの)があれば、原因を見つけられます。二分探索、仮説検証、計測はいずれも、そのシグナルを利用するだけです。シグナルがなければ、どれだけコードを見つめても解決には至りません。
ここに重点的に取り組んでください。積極的に、創造的に、あきらめずに取り組んでください。
構築方法(おおむね上から順に)
- バグに到達できる任意の境界で失敗するテストを作成します:ユニットテスト、統合テスト、E2E テスト。
- 起動中の開発サーバーに対する Curl/HTTP スクリプトを作成します。
- フィクスチャ入力を使って CLI を実行し、標準出力を既知の正常なスナップショットと比較します。
- UI を操作し、DOM/コンソール/ネットワークを検証するヘッドレスブラウザースクリプト(Playwright/Puppeteer)を作成します。
- 取得済みのトレースを再生します。実際のネットワークリクエスト/ペイロード/イベントログをディスクに保存し、コードパスに単独で再生します。
- 一時的なハーネスを作成します。システムの最小限の部分(1つのサービス、モック化した依存関係)を起動し、1回の関数呼び出しでバグのコードパスを実行します。
- プロパティテスト/ファズループを実行します。「出力が時々間違っている」バグなら、ランダムな入力を1000件実行し、失敗のパターンを探します。
- 二分探索用のハーネスを作成します。バグが既知の2つの状態(コミット、データセット、バージョン)の間で発生した場合、「状態 X で起動して確認し、繰り返す」処理を自動化し、
git bisect runで実行できるようにします。 - 差分ループを実行します。同じ入力を旧バージョンと新バージョン(または2つの設定)に通し、出力を比較します。
- HITL Bash スクリプトを使います。人がクリックする必要がある場合の最終手段として、
scripts/hitl-loop.template.shで人の操作を誘導し、ループを構造化した状態に保ちます。取得した出力は次の調査に活用します。
適切なフィードバックループを構築すれば、バグは90%解決したも同然です。
ループを強化する
ループをプロダクトとして扱ってください。ループを1つ構築したら、強化しましょう。
- もっと速くできますか?(セットアップをキャッシュする、関係のない初期化を省く、テスト範囲を絞る)
- シグナルをより明確にできますか?(「クラッシュしなかった」ではなく、具体的な症状をアサートする)
- より決定的にできますか?(時刻を固定する、乱数シードを設定する、ファイルシステムを分離する、ネットワークを固定する)
30秒かかる不安定なループは、ループがない状態とほとんど変わりません。2秒で実行できる決定的なループは、強力なデバッグ手段です。
非決定的なバグ
目標は、完全な再現手順ではなく、再現率を高めることです。トリガーを100回ループさせ、並列化し、負荷をかけ、タイミングの範囲を狭め、スリープを挿入してください。再現率が50%のバグならデバッグできますが、1%では困難です。デバッグ可能になるまで再現率を上げ続けてください。
どうしてもループを構築できない場合
いったん止めて、その旨を明確に伝えてください。試したことを列挙してください。ユーザーには次のいずれかを依頼してください:(a)再現できる環境へのアクセス、(b)編集済みの取得済み成果物(HAR ファイル、ログダンプ、コアダンプ、タイムスタンプ付きの画面録画)、または(c)本番環境に一時的な計測を追加する許可。ループなしで仮説を立てて先に進んではいけません。
完了条件:確実に失敗するタイトなループ
フェーズ 1 の完了条件は、ループがタイトで、失敗を検出できることです。すでに少なくとも一度は実行済みの1 つのコマンド(スクリプトのパス、テストの実行コマンド、curl など)を挙げられ、その実行コマンドと出力(機密情報は伏せる)を提示できる必要があります。また、そのコマンドは次の条件を満たす必要があります。
- 失敗を検出できる:実際のバグのコードパスを通り、ユーザーが訴えている正確な症状をアサートするため、このバグで失敗し、修正後は成功する可能性があります。「エラーなく実行できる」だけでは不十分です。_この特定のバグを検出できる_必要があります。
- 決定性がある:実行するたびに同じ判定になります(不安定なバグの場合は、上記のように再現率が高く固定されている必要があります)。
- 高速:数秒で完了し、数分はかかりません。
- エージェントが実行できる:人間がループに介入するのは
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] のような一意のプレフィックスを付けます。最後に 1 回 grep するだけで削除できます。タグのないログは残り、タグ付きログは削除できます。
パフォーマンスに関する分岐。 パフォーマンスのリグレッションでは、通常ログは適切ではありません。代わりに、ベースラインを測定する仕組み(計測用ハーネス、performance.now()、プロファイラー、クエリプランなど)を用意してから、二分探索します。まず測定し、修正はその後です。
フェーズ 5:修正+回帰テスト
修正を加える前に回帰テストを書きます。ただし、適切なテストの接点がある場合に限ります。
適切なテストの接点とは、呼び出し箇所で実際に発生する本物のバグパターンをテストで再現できる箇所です。利用できる接点が浅すぎる場合(複数の呼び出し元が必要なバグなのに単一の呼び出し元だけをテストする場合や、バグを引き起こした呼び出しの連鎖を再現できない単体テストなど)、そこで回帰テストを行うと誤った安心感を招きます。
**適切なテストの接点がないなら、それ自体が発見事項です。**記録してください。コードベースのアーキテクチャによって、バグを再発しないように防ぐことができません。次のフェーズでこの点を取り上げてください。
適切なテストの接点がある場合:
- 最小化した再現例を、その接点で失敗するテストにします。
- テストが失敗することを確認します。コードやフィクスチャを変更して意図的に失敗させた場合は、その変更が加わったと確信する前に、クリーンなコピーとの差分を
diffで確認します。 - 修正を適用します。
- テストが成功することを確認します。
- 元の(最小化していない)シナリオに対して、フェーズ 1 のフィードバックループを再実行します。
フェーズ 6:クリーンアップ
完了とする前に、以下が必要です。
- 元の再現例で問題が再現しなくなった(フェーズ 1 のループを再実行)
- 回帰テストが成功する(または適切なテストの接点がないことを記録済み)
-
[DEBUG-...]の計測コードをすべて削除した(プレフィックスをgrepで検索) - 一時的なプロトタイプを削除した(または、デバッグ用と明確に示した場所に移動した)
- 正しかった仮説をコミットメッセージまたは PR メッセージに記載し、次にデバッグする人が学べるようにした