matt-diagnosing-bugs について
matt-diagnosing-bugs は、難しいソフトウェア障害を調べる開発者向けの体系的なデバッグスキルです。報告された症状を判定できる再現ループを先に作り、再現条件の縮小、仮説検証、デバッグ痕跡の削除へと進みます。
主な機能
- 01合否判定できる再現ループを構築
- 02障害を再現して条件を絞り込む
- 03反証可能な仮説を順位付け
- 04対象を絞った調査で仮説を検証
- 05回帰テストを追加して後片付け
活用シーン
- 01断続的または再現困難な障害の調査
- 02複数の原因候補の切り分け
- 03修正内容の回帰テスト追加
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ learnprompt/ laoyeye --skill matt-diagnosing-bugsインストール後、Claude Code で/matt-diagnosing-bugsを入力して読み込まれたか確認します。
同梱ファイルagents/openai.yamlscripts/hitl-loop.template.sh
詳細
バグの診断
難解なバグに取り組むための手法です。明確な理由がある場合に限り、フェーズを省略してください。
コードベースを調査するときは、関連するモジュールの全体像を把握するため、CONTEXT.md(存在する場合)を読み、変更する箇所に関する ADR も確認してください。
編集
このスキルでは、コマンド、出力、取得した成果物を提示します。まず、すべてのシークレットを編集してください。該当箇所には <REDACTED> と記載します。認証情報が提示内容に含まれず環境内にとどまるよう、環境変数を使ってループを構築してください。取得した成果物には認証ヘッダーが含まれます。シグナルを示す行だけを引用してください。
編集済みの出力だけではバグを診断できない場合は、その旨を伝えてユーザーに確認してください。
フェーズ 1:フィードバックループを構築する
これがこのスキルの要です。 バグに対する明確な合否シグナル(このバグで失敗するもの)があれば、原因を突き止められます。二分探索、仮説検証、計測はいずれも、そのシグナルを利用するだけです。シグナルがなければ、コードをいくら見つめても解決には至りません。
ここに労力を惜しまず注いでください。積極的に取り組み、創意工夫し、決して諦めないでください。
おおむね以下の順に構築する方法
- バグに到達する任意の境界で失敗するテスト:単体テスト、統合テスト、E2E テスト。
- 起動中の開発サーバーに対する Curl/HTTP スクリプト。
- フィクスチャ入力を使った CLI 呼び出しと、標準出力を既知の正常なスナップショットと比較する方法。
- UI を操作し、DOM/コンソール/ネットワークを検証するヘッドレスブラウザースクリプト(Playwright/Puppeteer)。
- 取得済みトレースの再生。実際のネットワークリクエスト/ペイロード/イベントログをディスクに保存し、コードパスに沿って単独で再生します。
- 使い捨てハーネス。システムの最小限の構成(1 つのサービスとモック化した依存関係)を立ち上げ、単一の関数呼び出しでバグのコードパスを実行します。
- プロパティ/ファジングループ。「出力がときどき間違う」バグなら、ランダムな入力を 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 など)を挙げられ、その実行コマンドと出力(機密情報は伏せる)を提示できる必要があります。また、そのコマンドは次の条件を満たす必要があります。
- 失敗を検出できる:実際のバグのコードパスを通り、ユーザーが訴えている正確な症状をアサートするため、このバグで失敗し、修正後は成功する可能性があります。「エラーなく実行できる」だけでは不十分です。_この特定のバグを検出できる_必要があります。
- 決定性がある:実行するたびに同じ判定になります(不安定なバグの場合は、上記のように再現率が高く固定されている必要があります)。
- 高速:数秒で完了し、数分はかかりません。
- エージェントが実行できる:人間がループに介入するのは
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:修正+リグレッションテスト
修正前にリグレッションテストを作成します。ただし、適切なシームがある場合に限ります。
適切なシームとは、呼び出し箇所で発生する実際のバグのパターンをテストで再現できる場所です。利用可能なシームが浅すぎる場合(バグの発生に複数の呼び出し元が必要なのに、単一の呼び出し元しかテストしない場合や、バグの原因となった呼び出しの連鎖を再現できない単体テストなど)、そこでリグレッションテストを作成すると、誤った安心感につながります。
**適切なシームがない場合、それ自体が調査結果です。**その旨を記録してください。コードベースのアーキテクチャによって、バグを再発しないように固定できない状態です。次のフェーズに向けて、この点を報告してください。
適切なシームがある場合:
- 最小化した再現ケースを、そのシームで失敗するテストにします。
- テストが失敗することを確認します。
- 修正を適用します。
- テストが成功することを確認します。
- 元の(最小化していない)シナリオに対して、フェーズ 1 のフィードバックループを再実行します。
フェーズ 6:クリーンアップ
完了を宣言する前に、以下が必要です。
- 元の再現ケースで問題が再現しなくなっている(フェーズ 1 のループを再実行する)
- リグレッションテストが成功している(または、シームがないことを記録している)
-
[DEBUG-...]の計測コードがすべて削除されている(プレフィックスをgrepする) - 使い捨てのプロトタイプが削除されている(または、デバッグ用と明示した場所に移動されている)
- 正しいと判明した仮説がコミット/PR のメッセージに記載されている。次のデバッガーが学べるようにするためです。