# Diagnosing Bugs

> 難しいバグやパフォーマンス劣化のための診断ループ。ユーザーが「診断して」「デバッグして」と言ったとき、または壊れている・例外が出る・失敗する・遅いといった報告をしたときに使う。

- Skill: `sinnlosses/diagnosing-bugs` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add sinnlosses/diagnosing-bugs`
- Raw SKILL.md: https://api.skillmd.com/api/skills/sinnlosses/diagnosing-bugs/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: sinnlosses (https://skillmd.com/u/sinnlosses)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/sinnlosses/diagnosing-bugs

---


# バグの診断

難しいバグに対する規律。フェーズを省略するのは、明示的に正当化できる場合のみ。

コードベースを探索する際は、`CONTEXT.md` が存在すればそれを読み、関連モジュールの明確なメンタルモデルを得る。また、これから触る領域のADRを確認する。

## 秘匿情報のマスキング

このスキルでは、コマンド・出力・取得したアーティファクトを提示する。**最初にすべての秘匿情報をマスキングする**こと: その場所に `<REDACTED>` と書く。ループは環境変数に対して構築し、認証情報が表示内容ではなく環境側に留まるようにする。取得したアーティファクトには認証ヘッダーが含まれることがあるので、シグナルとなる行だけを引用する。

マスキング後の出力だけでは診断に不十分な場合は、その旨を伝えてユーザーに確認する。

## フェーズ1: フィードバックループを構築する

**これがこのスキルの本質である。** それ以外はすべて機械的な作業にすぎない。この*特定の*バグでredになる、締まったpass/failシグナルさえあれば、必ず原因にたどり着く。二分探索・仮説検証・計装は、そのシグナルを消費するだけの存在である。それがなければ、どれだけコードを見つめても救われない。

ここに不釣り合いなほどの労力をかけること。**貪欲に。創造的に。決してあきらめない。**

### 構築の方法（おおむねこの順序で）

1. バグに到達する任意のシームでの**失敗するテスト**: 単体、結合、e2e。
2. 稼働中の開発サーバーに対する**curl / HTTPスクリプト**。
3. 既知の正しいスナップショットとstdoutを比較する、フィクスチャ入力を使った**CLI呼び出し**。
4. UIを操作しDOM/コンソール/ネットワークをアサーションする**ヘッドレスブラウザスクリプト**（Playwright / Puppeteer）。
5. **キャプチャしたトレースの再生。** 実際のネットワークリクエスト/ペイロード/イベントログをディスクに保存し、そのコード経路単体で再生する。
6. **使い捨てのハーネス。** バグのあるコード経路を1回の関数呼び出しで行使できる、システムの最小限のサブセット（1サービス、依存はモック）を立ち上げる。
7. **プロパティ/ファジングループ。** バグが「時々おかしな出力になる」というものなら、ランダムな入力を1000個試し、失敗パターンを探す。
8. **二分探索ハーネス。** バグが既知の2つの状態（コミット、データセット、バージョン）の間で発生した場合、「状態Xで起動→確認→繰り返し」を自動化し、`git bisect run` できるようにする。
9. **差分ループ。** 同じ入力を旧バージョンと新バージョン（または2つの設定）の両方に通し、出力を比較する。
10. **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個のランク付けされた仮説**を生成する。単一の仮説だけを生成すると、最初に思いついたもっともらしいアイデアに固執してしまう。

各仮説は**反証可能**でなければならない: それが立てる予測を明記する。

> 形式: 「もし<X>が原因なら、<Yを変えると>バグが消える／<Zを変えると>バグが悪化する。」

その予測を言葉にできないなら、その仮説は勘にすぎない。捨てるか、研ぎ澄ますこと。

**検証する前に、ランク付けした一覧をユーザーに見せる。** ユーザーは即座に順位を入れ替えるドメイン知識を持っていることが多い（「#3には最近デプロイで変更を入れた」など）、あるいは既に除外済みの仮説を知っていることもある。安いチェックポイントで、大きな時間の節約になる。これをブロッキングにする必要はない。ユーザーが離席中なら、自分のランク付けのまま進める。

## フェーズ4: 計装する

各プローブはフェーズ3の特定の予測に対応させる。**一度に1つの変数だけを変える。**

ツールの優先順位:

1. 環境がサポートしていれば**デバッガ/REPLでの検査**。1つのブレークポイントは10個のログに勝る。
2. 仮説を区別できる境界での**狙いを定めたログ**。
3. 「全部ログに出してgrepする」は絶対にしない。

**すべてのデバッグログに一意のプレフィックスをタグ付けする**（例: `[DEBUG-a4f2]`）。最後の掃除が1回のgrepで済むようになる。タグ付けされていないログは生き残ってしまうが、タグ付けされたログは死ぬ（消せる）。

**パフォーマンス分岐。** パフォーマンス劣化の場合、ログは大抵役に立たない。代わりに: ベースライン計測（計測ハーネス、`performance.now()`、プロファイラ、クエリプラン）を確立してから二分探索する。まず計測、修正はその後。

## フェーズ5: 修正と回帰テスト

修正の**前に**回帰テストを書く。ただし**正しいシーム**が存在する場合に限る。

正しいシームとは、そのバグの実際のパターンを、呼び出し箇所で発生する通りに行使できるテストのことである。利用可能なシームが浅すぎる場合（複数呼び出し元が必要なバグに対する単一呼び出し元のテスト、バグを引き起こした連鎖を再現できない単体テストなど）、そこに回帰テストを置いても偽の安心感しか得られない。

**正しいシームが存在しないなら、それ自体が発見である。** それを書き留める。コードベースのアーキテクチャが、バグを封じ込めることを妨げている。これを次のフェーズのために記録する。

正しいシームが存在するなら:

1. 最小化した再現を、そのシームでの失敗するテストに変える。
2. 失敗するのを確認する。
3. 修正を適用する。
4. 成功するのを確認する。
5. 元の（最小化していない）シナリオに対して、フェーズ1のフィードバックループを再実行する。

## フェーズ6: 後片付け

完了を宣言する前に必須:

- [ ] 元の再現がもう再現しない（フェーズ1のループを再実行して確認）
- [ ] 回帰テストが通る（またはシームが存在しないことが文書化されている）
- [ ] `[DEBUG-...]` の計装がすべて除去されている（プレフィックスをgrepして確認）
- [ ] 使い捨てのプロトタイプが削除されている（または明確にマークされたデバッグ用の場所に移されている）
- [ ] 正しかった仮説がコミット/PRメッセージに記載されており、次にデバッグする人が学べるようになっている

