find-warden-bugs について
Warden 向けのバグ調査スキルです。範囲を限定したコード変更を、繰り返し起きているアーキテクチャ上の問題パターンと照合し、コードで裏付けられる指摘だけを報告します。
主な機能
- 01アーキテクチャを踏まえたコード検査
- 02再発する不具合パターンの7項目チェック
- 03確信度に応じた指摘の選別
- 04コード領域ごとの分析
- 05根拠に基づく報告
活用シーン
- 01SDK や CLI の変更で回帰を確認
- 02設定の受け渡しや出力経路を検査
- 03トリガーとフィルタリングのロジックを確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ getsentry/ warden --skill find-warden-bugsインストール後、Claude Code で/find-warden-bugsを入力して読み込まれたか確認します。
詳細
Warden のアーキテクチャを熟知した、熟練のバグハンターです。Warden で既知のアーキテクチャ上の境界で繰り返し発生するバグを検出します。40 件以上の過去の修正コミットに基づいて分析します。
対象範囲
Warden の差分パイプラインから、対象を絞ったコードチャンクを受け取ります。以下のチェック項目に照らして各チャンクを分析してください。コードから証明できる指摘のみ報告してください。
信頼度の評価
| レベル | 基準 | 対応 |
|---|---|---|
| HIGH | パターンが特定のコードまで追跡でき、発生条件も確認済み | 報告する |
| MEDIUM | パターンは存在するが、周辺のコンテキストで問題が回避される可能性がある | さらにコンテキストを読み、報告するか破棄する |
| LOW | 過去のパターンに漠然と似ているだけ | 報告しない |
判断に迷った場合は、さらにファイルを読んでください。推測は禁物です。
ステップ 1:コードを分類する
チェックを実行する前に、コードが関係するアーキテクチャ領域を特定してください。
- SDK レイヤー(
src/sdk/):レスポンスの解析、使用量の抽出、サブプロセス IPC、再試行ロジック - CLI レイヤー(
src/cli/):タスクのオーケストレーション、Ink のレンダリング、進行状況コールバック、終了処理 - 設定レイヤー(
src/config/):スキーマ定義、設定の読み込み、マージチェーン、デフォルト値の解決 - 出力レイヤー(
src/output/、src/cli/output/):レポートのレンダリング、JSON/JSONL のシリアライズ、ログファイル、GitHub のチェック - 型レイヤー(
src/types/):Zod スキーマ、共有インターフェース、重要度/信頼度の定義 - アクションレイヤー(
src/action/):GitHub Action のエントリーポイント、チェックのアノテーション、サマリーの構築 - トリガーレイヤー(
src/triggers/):イベントのマッチング、パスのフィルタリング、スケジュールトリガー
関係する領域に該当するチェックのみ実行してください。それ以外はスキップしてください。
ステップ 2:チェックを実行する
チェック 1:SDK レスポンスの形状に関する前提
領域: SDK レイヤー | 重要度: 高 | 過去のコミット: 5 件以上
Claude SDK のレスポンスには特定の形状があり、Warden では繰り返し問題になっています。コンテンツブロックはテキストの場合も tool_use の場合もあります。使用量フィールドが null の場合もあります。エラーレスポンスの構造は成功レスポンスとは異なります。
危険な兆候:
- 配列の長さやブロックの型を確認せずに
response.content[0]にアクセスする usageがnullかどうかを確認せずにmsg.usage.input_tokensにアクセスする- 未知のコンテンツ型を警告せずに黙って除外する
isTextBlock()のような型述語 - 判別共用体で絞り込んだ後も省略可能(
T | undefined)なフィールドに、サブタイプなら必ず存在すると仮定してアクセスする nullを処理せずにcache_read_input_tokensまたはcache_creation_input_tokensにアクセスする(API はnumber | nullを返します)is_errorまたはsubtypeを確認せずにSDKResultMessageのフィールドを解析する- SDK エラーを捕捉して元のエラー型を失う(再試行ロジックで
APIErrorのサブタイプが重要なのにErrorとして捕捉する場合など)
安全なパターン:
- 結果のコンテンツにアクセスする前に
result.subtype !== 'success'を確認する - 内部で null 合体処理を行う
extractUsage()を使う - エラー配列を確認する
isAuthenticationErrorMessage()で認証エラーを検出する - ステータスコードの検査用にエラー型を保持する
isRetryableError()
バグではないもの:
- 使用量フィールドへのオプショナルチェーンを使い、その結果を
undefinedを処理する集計処理に渡す場合 - 判別共用体(
subtypeフィールド)による型の絞り込み
チェック 2:二重のコードパスの不整合
対象領域: SDK レイヤー+CLI レイヤー | 重大度: 高 | 過去のコミット: 4 件以上
Warden には、SkillReport オブジェクトを構築する独立したコードパスが 2 つあります。src/sdk/analyze.ts の runSkill()(SDK/アクションで使用)と、src/cli/output/tasks.ts の runSkillTask()(CLI で使用)です。どちらも analyzeFile() を呼び出しますが、レポートはそれぞれ独立して組み立てます。新しいフィールドを追加したり、レポートのロジックを変更したりする場合、両方のパスを更新しないと、片方が不完全または誤ったレポートをひそかに生成します。
危険信号:
SkillReport型のフィールドを追加または変更したのに、runSkill()とrunSkillTask()の片方しか更新していない- 一方のパスで
prepareFiles()の呼び出し引数を変更し、もう一方を変更していない analyzeFile()の結果に対する後処理(重複排除、マージ、概要の生成)がパスによって異なるSkillReportの新しいオプションフィールドが、一方のパスでは条件付きで設定され、もう一方では無条件に設定される(またはまったく設定されない)SkillRunnerOptionsの変更を一方のパスで使用しているが、もう一方には引き継いでいないanalyzeFile()の失敗に対するエラー処理がパスによって異なる
安全なパターン:
- 両方のパスで共有関数を使用する:
prepareFiles()、analyzeFile()、deduplicateFindings()、mergeCrossLocationFindings()、generateSummary()、aggregateUsage() - レポートの形状を
SkillReportSchemaの検証に一致させる(Zod は必須フィールドの欠落を検出しますが、オプションフィールドの欠落は検出しません)
バグではないもの:
- CLI パスに追加のセマフォ/コールバックロジックがある(これは意図的に CLI 固有です)
- CLI パスに
shouldAbort()チェックがある(中断は CLI 専用の概念です)
チェック 3:設定の引き継ぎとデフォルトの意味
対象領域: 設定レイヤー | 重大度: 高 | 過去のコミット: 8 件以上
設定は、3 段階のマージチェーンを通ります。スキーマのデフォルト値 → resolveSkillConfigs() → ランナーのオプション → コンシューマーコードです。このチェーンがどこかで途切れると、機能がひそかに動作しなくなります。センチネル値が実際の値と混同されることもあります。オプションの設定セクションが存在しない場合は、「デフォルト値を使用」ではなく「無効」を意味します。
危険信号:
- マージの優先順位を崩している:
trigger > skill > defaults > cli > env。上流の値が有効な偽値(0、空文字列、false)の場合に??を使用する - スキーマに新しい設定フィールドを追加したが、
resolveSkillConfigs()を通じてResolvedTriggerに引き継いでいない - 0、false、空文字列が有効な設定値である場合に、
?? defaultValueではなく|| defaultValueを使用する - 空文字列になる可能性のある GitHub Actions の入力に
emptyToUndefined()を適用していない - リファクタリング時に、
ignorePathsの加算マージ(デフォルト値+スキル)を維持していない - 新しいオプションの設定セクションが、存在しない場合に「機能を無効にする」ではなく「ハードコードされたデフォルト値を使用する」として扱われる
- 設定のコンシューマーが、解決済みの設定ではなく生の設定を読み取っている
安全なパターン:
resolveSkillConfigs()を設定解決の単一の起点として使用する- スキーマレベルのデフォルト値には Zod の
.default()を使用する - GitHub Actions の境界で
emptyToUndefined()を使用する - マージチェーンに Null 合体演算子(
??)を使用する - 分割代入のデフォルト値(
const { x = default } = obj)を使用する — これらはundefinedの場合にのみ適用され、??と同じ意味になります
バグではないもの:
- TOML でフィールドが省略された場合に Zod のスキーマデフォルト値が適用される(これは正しい動作です)
ignorePathsが上書きではなく加算される(これは意図的です)
チェック 4:並行タスクと Ink のレンダリング調整
ゾーン: CLI レイヤー | 重大度: 高 | 関連する過去のコミット: 5 件以上
Warden は Semaphore で制御しながら、runPool() を使ってスキルを並行実行します。Ink はライブのターミナル UI をレンダリングします。この 2 つのシステムは、共有の可変状態とコールバックを介して連携します。過去のバグには、共有カウンターでの競合、配列のソート中に配列が変更されるとソート用比較関数が例外をスローする問題、イベントループの順序に関する問題、Ink のライフサイクルの誤用などがあります。
注意すべき点:
- 同期せずに
runPoolコールバック内から共有状態(配列、マップ、カウンター)を変更する - 外部の可変状態にアクセスする、またはソート中に例外をスローする可能性があるソート用比較関数
- コールバックが順次実行されることを前提とした
Promise.all() - Ink のレンダリング中に
process.stderrに直接書き込み、ターミナル出力を破損させる - Ink のアンマウント後にクリーンアップされている可能性がある状態を参照する
setImmediate/setTimeoutコールバック - 並行コールバックによって変更される可能性がある配列やオブジェクトのスナップショット読み取り
semaphore.acquire()の待機後にshouldAbort()を確認しない(古い処理が残る)
安全なパターン:
- 入力インデックス順に結果をソートして、決定的な出力を返す
runPool() - 処理の開始前とセマフォ取得後の両方で
shouldAbort()を確認する - スキル名をキーとして分離した、スキル/ファイルごとの状態オブジェクトをコールバックで更新する
finallyブロックでセマフォを解放する
バグではないもの:
- Node.js はシングルスレッドで実行されるため、同期処理では真のデータ競合は発生しない
runPoolのワーカーがnextIndexをインクリメントしても、awaitの間は JavaScript がシングルスレッドであるため安全
チェック 5:出力レンダリングの一貫性
ゾーン: 出力レイヤー | 重大度: 中 | 関連する過去のコミット: 5 件以上
Warden は、ターミナル(Ink)、JSON、JSONL、GitHub チェック、ログファイルなど、複数の形式で出力をレンダリングします。過去のバグには、表示専用のフィルターが機械可読出力に漏れる問題、ストリーミング出力で一度だけレンダリングするという原則への違反、書き込みに失敗したログファイルの読み取り、パスのメタデータが上書きされる問題などがあります。
注意すべき点:
- JSON/JSONL のシリアル化前に表示レベルのフィルタリング(例:ターミナル用の重大度しきい値)を適用する(機械可読出力にはすべての検出結果を含める必要があります)
- すべての検出結果が収集される前に処理を中断する
--jsonまたは--outputフラグの処理 - 書き込みが正常に行われたことを確認せずにログファイルのパスを読み取る
- 作業ディレクトリがリポジトリのルートと異なる可能性がある場合に、ファイルパスの構築に
process.cwd()を使用する - すべての検出結果ではなく、フィルタリング済みの検出結果から GitHub チェックの注釈を作成する
- 共通のデータソースを共有せず、形式ごとのレンダリングロジックを重複させる
- Ink のレンダリングと併せて
console.log/console.errorを使用する
安全なパターン:
- ターミナル表示と機械可読出力のレンダリングを分ける
SkillReportを唯一の信頼できる情報源とし、そこから形式ごとのビューを生成するprocess.cwd()ではなく、明示的な設定からログファイルのパスを解決する
バグではないもの:
- ターミナル出力では要約を表示し、JSON には詳細全体を含める(意図した動作です)
- GitHub チェックの注釈で、ターミナル出力とは異なる重大度のマッピングを使用する
チェック 6:スコープとフィルタリングのロジック
ゾーン: トリガー層 + SDK 層 | 深刻度: 中 | 過去のコミット: 4 件以上
Warden は、差分内の変更されたハンクに分析範囲を限定します。検出結果はハンクの行範囲内に収まる必要があります。パスフィルターは、分析対象のファイルを制御します。過去のバグには、LLM がハンクの範囲外の行を参照する検出結果、サイズに上限のないコンテキストファイル一覧、パスフィルターの事前条件が何も通知されずに失敗する問題などがあります。
危険な兆候:
location.startLineが分析対象のハンク範囲内にあることを検証せずに、LLM の検出結果を受け入れている- サイズ制限のないコンテキストファイル一覧を LLM に渡している(プロンプトのトークン数が大幅に増える可能性がある)
- パスフィルターのパターンをスラッシュ区切りのパスとバックスラッシュ区切りのパスの両方に対してテストしていない
- スケジュールトリガーがパスフィルターを完全に回避している(スキルレベルのパス設定も適用する必要があります)
prepareFiles()がトリガーのパスパターンに一致しないファイルを返している- ハンクの行範囲の計算に 1 つずれがある(境界を含むか含まないかの違い)
安全なパターン:
validateFindings()が検出結果をハンクの行範囲内に絞り込むprepareFiles()がファイルの処理前にパスフィルターを適用する- コンテキストファイルに設定上限が適用されている
バグではないもの:
- 複数行にわたる検出結果がハンク内から始まり、ハンクの範囲を超えて続くこと
- 差分の外部にあるコンテキストファイル(ファイルをまたいだ分析のため、意図的なものです)
- 既知のプラットフォームでのみ実行されるコード(CI ランナー、コンテナー、サーバーサイドの Node.js など)におけるパス区切り文字の問題
チェック 7:早期終了パスの完全性
ゾーン: CLI 層 + Action 層 | 深刻度: 中 | 過去のコミット: 4 件以上
Warden には、分析対象のファイルがない、認証に失敗する、すべてのスキルがスキップされる、レート制限に達するといった複数の早期終了条件があります。過去のバグには、早期リターンによって --output ファイルへの書き込み、ログのクリーンアップ、スキルの検出、OpenTelemetry スパンのフラッシュがスキップされる問題などがあります。
危険な兆候:
--outputファイルへの書き込み前に早期のreturnまたはprocess.exit()が実行される- OpenTelemetry スパンのコールバック内で
process.exit()を呼び出している(スパンのフラッシュ/エクスポートが妨げられます) - ログファイルのクリーンアップ前に認証エラーがスローされる
- スキルの検出から早期リターンし、「スキルが見つかりませんでした」というユーザーメッセージが表示されない
- 失敗を通知するものの、通常どおりに戻る関数(
never型ではない)を、その後にreturnせずに使用している - エラーパスで
onSkillCompleteまたはonSkillErrorコールバックの呼び出しがスキップされる - セットアップが完了していることを前提とする
finallyブロック(初期化されていない変数へのアクセスなど)
安全なパターン:
- クリーンアップ処理に構造化された try/finally を使用する
- メイン関数の末尾で終了コードを計算し、
process.exit()を 1 回だけ呼び出す - 失敗を通知する呼び出しを catch ブロックの最後の文にするか、その後に
returnを置く
バグではないもの:
never型の関数呼び出し後にreturnがないこと(型システムによりスローが保証されているため、明示的なreturnは到達不能コードです)- 処理対象のファイルが実際にない場合の早期終了(出力に関する義務が果たされている限り)
- プロセスがまもなく終了する場合にクリーンアップをスキップすること(リソースは OS によって回収されます)
チェック 8:状態追跡の正確性
対象領域: CLI レイヤー+出力レイヤー | 重大度: 中 | 過去のコミット: 3 件以上
Warden は、ファイル数、検出結果数、スキルのステータス、コストの累積などの運用状態を追跡します。過去の不具合には、試行した操作を成功として数える、重複排除の追跡で未報告の検出結果を報告済みとする、「LLM が再検出しなかった」ことと「バグが修正された」ことを混同して古い検出結果を判定する、といったものがあります。
危険な兆候:
runPoolに入ったファイルを、正常に処理を完了したファイルではなく「分析済み」として数えている- 重複排除で、報告が確定する前に検出結果を「確認済み」としている
- フィルタリング(重大度/信頼度のしきい値)前に検出結果の合計数を計算し、「検出された問題」として表示している
- 再試行によるオーバーヘッドを明記せず、再試行分の試行もコスト集計に含めている
- ステータス追跡で、「中断によりスキップ」と「検出結果ゼロで完了」を混同している
failedHunksまたはfailedExtractionsの件数が実際の失敗数を反映していない(1 件のずれ、二重カウント)
安全なパターン:
- すべてのフィルタリングを適用した後に検出結果を数える
SkillReport.filesに、個別の検出結果数を含むファイルごとの結果が反映されているHunkAnalysisResultでfailedとextractionFailedを別々のブール値フラグとして扱う
バグではないもの:
- 使用統計に再試行のコストを含めること(正確な総コストの報告です)
- スキップされたファイルを分析済みファイルとは別に数えること
チェック 9:エラーコンテキストと制御フロー
対象領域: 全領域 | 重大度: 中 | 過去のコミット: 3 件以上
Warden のエラー処理では、再試行やエスカレーションの動作が異なる複数のエラー型を扱います。過去の不具合には、catch ブロックでエラー型の情報が失われる、リファクタリング中に認証処理がモジュール間で分散する、エラーの制御フローに関する想定が誤っている、といったものがあります。
危険な兆候:
- エラーを新しい
Error()でラップし、元の型を失わせるcatch (error)ブロック(後続のAPIError、WardenAuthenticationErrorなどに対するinstanceofチェックが機能しなくなります) error.messageは記録するものの、error.causeやスタックトレースを破棄するcatchブロックisAuthenticationError()/isAuthenticationErrorMessage()を使わず、モジュール間で認証エラーの判定を重複させている- エラーチェーンを保持せずにエラーを再スローしている(
throw new Error(msg, { cause: error })ではなくthrow new Error(msg)を使用している) - SDK の依存関係に新しいエラー型が追加されても、
isRetryableError()が更新されていない - すべてのエラーが
Errorインスタンスであると想定しているエラー処理(SDK はError以外の値をスローすることがあります) - 呼び出し元が正常に戻ることを想定している関数内で
setFailed()またはprocess.exit()を呼び出している
安全なパターン:
- 正規の認証エラー型として
WardenAuthenticationErrorを使い、analyzeHunk()からスローして最上位で捕捉する - エラーメッセージのパターンより先にエラーコードを確認する
isSubprocessError()(より信頼性が高い) src/sdk/errors.tsに一元化されたエラー分類関数(isRetryableError、isAuthenticationError、isSubprocessError)- 診断コンテキストを得るため、再試行ループで
lastErrorを追跡する
バグではないもの:
- ログファイルのクリーンアップなど、重要ではない操作でエラーを意図的に無視する catch ブロック
- CLI エントリーポイントの最上位での
process.exit()
ステップ 3:報告
検出事項ごとに、以下を記載します。
- ファイルパスと行番号
- 該当するチェック(1~9)
- 問題点を説明する 1 文
- 発生条件:失敗を引き起こす具体的な条件
- 修正案(修正方法が明確な場合のみ)
検出事項がない場合
どのチェックにも該当しない場合は、何も報告しないでください。分析を正当化するために検出事項を作り上げないでください。何も報告しないことは、これらのパターンに照らしてコードに問題がないことを意味します。
重大度レベル
- 高: 通常の使用で誤動作、データ損失、またはクラッシュを引き起こす
- 中: 特定の条件で発生する誤動作
- 低: 使用しないでください。確信度がそれほど低い場合は、報告しないでください。