root-cause-tracing について
コールスタックの深い位置で発生する不具合を調べる開発者向けデバッグスキルです。呼び出しと値をさかのぼって発生源を特定し、複数の層に検証を加える手順を示します。
主な機能
- 01呼び出しをさかのぼって原因を特定
- 02スタックトレースによる診断情報の追加
- 03汚染を起こすテストの特定
- 04複数層での検証追加
活用シーン
- 01実行中の深い箇所で起きるエラーの調査
- 02無効な入力の発生源の特定
- 03ファイルシステムを汚染するテストの特定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jackspace/ claudeskillz --skill root-cause-tracingインストール後、Claude Code で/root-cause-tracingを入力して読み込まれたか確認します。
同梱ファイルfind-polluter.shSKILL.json
詳細
根本原因の追跡
概要
バグはコールスタックの深い場所で表面化することがよくあります(誤ったディレクトリでの git init、誤った場所へのファイル作成、誤ったパスでのデータベースのオープンなど)。エラーが現れた場所を修正したくなりますが、それでは症状に対処しているだけです。
基本原則: 呼び出しチェーンをさかのぼり、最初のトリガーを見つけてから、発生源で修正します。
使用する場面
digraph when_to_use {
"Bug appears deep in stack?" [shape=diamond];
"Can trace backwards?" [shape=diamond];
"Fix at symptom point" [shape=box];
"Trace to original trigger" [shape=box];
"BETTER: Also add defense-in-depth" [shape=box];
"Bug appears deep in stack?" -> "Can trace backwards?" [label="yes"];
"Can trace backwards?" -> "Trace to original trigger" [label="yes"];
"Can trace backwards?" -> "Fix at symptom point" [label="no - dead end"];
"Trace to original trigger" -> "BETTER: Also add defense-in-depth";
}
次のような場合に使用します:
- 実行の深い場所でエラーが発生する(エントリーポイントではない)
- スタックトレースに長い呼び出しチェーンが表示される
- 無効なデータの発生元が不明
- 問題を引き起こすテストやコードを特定する必要がある
追跡の手順
1. 症状を確認する
Error: git init failed in /Users/jesse/project/packages/core
2. 直接の原因を特定する
この原因となっているコードは何ですか?
await execFileAsync('git', ['init'], { cwd: projectDir });
3. 何がこれを呼び出したのかを確認する
WorktreeManager.createSessionWorktree(projectDir, sessionId)
→ called by Session.initializeWorkspace()
→ called by Session.create()
→ called by test at Project.create()
4. 呼び出しチェーンをさかのぼる
どの値が渡されましたか?
projectDir = ''(空文字列です!)cwdに空文字列を指定すると、process.cwd()に解決されます- それがソースコードのディレクトリです!
5. 最初のトリガーを特定する
空文字列はどこから来ましたか?
const context = setupCoreTest(); // Returns { tempDir: '' }
Project.create('name', context.tempDir); // Accessed before beforeEach!
スタックトレースを追加する
手動で追跡できない場合は、計測用のコードを追加します:
// Before the problematic operation
async function gitInit(directory: string) {
const stack = new Error().stack;
console.error('DEBUG git init:', {
directory,
cwd: process.cwd(),
nodeEnv: process.env.NODE_ENV,
stack,
});
await execFileAsync('git', ['init'], { cwd: directory });
}
重要: テストでは logger ではなく console.error() を使用してください(表示されないことがあるため)
実行して結果を取得する:
npm test 2>&1 | grep 'DEBUG git init'
スタックトレースを分析する:
- テストファイル名を探します
- 呼び出しを引き起こす行番号を見つけます
- パターンを特定します(同じテストか? 同じパラメーターか?)
汚染を引き起こすテストの特定
テスト中に何かが現れるものの、どのテストが原因かわからない場合:
二分探索スクリプトを使用します:@find-polluter.sh
./find-polluter.sh '.git' 'src/**/*.test.ts'
テストを1つずつ実行し、最初に汚染を引き起こしたテストで停止します。使用方法についてはスクリプトを参照してください。
実例:空の projectDir
症状: .git が packages/core/(ソースコード)に作成される
追跡チェーン:
git initがprocess.cwd()で実行される ← cwd パラメーターが空- WorktreeManager に空の projectDir が渡される
- Session.create() に空文字列が渡される
- beforeEach の前にテストが
context.tempDirにアクセスする - setupCoreTest() が最初に
{ tempDir: '' }を返す
根本原因: 空の値にアクセスするトップレベル変数の初期化
修正: beforeEach の前にアクセスされた場合に例外をスローする getter に tempDir を変更
多層防御も追加:
- レイヤー 1:Project.create() がディレクトリを検証
- レイヤー 2:WorkspaceManager が空でないことを検証
- レイヤー 3:NODE_ENV ガードが tmpdir 以外での git init を拒否
- レイヤー 4:git init の前にスタックトレースをログ出力
重要な原則
digraph principle {
"Found immediate cause" [shape=ellipse];
"Can trace one level up?" [shape=diamond];
"Trace backwards" [shape=box];
"Is this the source?" [shape=diamond];
"Fix at source" [shape=box];
"Add validation at each layer" [shape=box];
"Bug impossible" [shape=doublecircle];
"NEVER fix just the symptom" [shape=octagon, style=filled, fillcolor=red, fontcolor=white];
"Found immediate cause" -> "Can trace one level up?";
"Can trace one level up?" -> "Trace backwards" [label="yes"];
"Can trace one level up?" -> "NEVER fix just the symptom" [label="no"];
"Trace backwards" -> "Is this the source?";
"Is this the source?" -> "Trace backwards" [label="no - keeps going"];
"Is this the source?" -> "Fix at source" [label="yes"];
"Fix at source" -> "Add validation at each layer";
"Add validation at each layer" -> "Bug impossible";
}
エラーが現れた場所だけを修正してはいけません。 最初のトリガーを見つけるまでさかのぼってください。
スタックトレースのヒント
テストでは: logger ではなく console.error() を使用してください。logger は出力が抑制されることがあります
操作の前に: 危険な操作が失敗した後ではなく、実行する前にログを出力します
コンテキストを含める: ディレクトリ、cwd、環境変数、タイムスタンプ
スタックを取得する: new Error().stack で呼び出しチェーン全体を確認できます
実際の効果
デバッグセッション(2025-10-03)より:
- 5段階の追跡で根本原因を発見
- 発生源で修正(getter による検証)
- 4層の防御を追加
- 1847 件のテストに合格、汚染ゼロ