sentry-backend-bugs について
本番で確認された不具合パターンに基づき、Sentry のバックエンド変更をレビューするスキルです。関連するコード経路を追跡し、中・高確度の指摘と修正案または確認事項を報告します。
主な機能
- 01不具合カテゴリ別のコード確認
- 02呼び出しとデータフローの追跡
- 03実行時・データ処理の問題を網羅
- 04中・高確度の指摘のみ報告
- 05安全な処理パターンの提示
活用シーン
- 01ブランチ内のバックエンド変更のレビュー
- 02ORM クエリのレコード欠落リスク確認
- 03入力解析とデータベース整合性の調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ getsentry/ sentry --skill sentry-backend-bugsインストール後、Claude Code で/sentry-backend-bugsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
README.mdreferences/concurrency-bugs.mddata-validation.mddatabase-integrity.mdintegration-errors.mdmissing-records.mdnull-and-type-errors.mdquery-validation.mdurl-safety.md詳細
Sentry バックエンドのバグパターンレビュー
最も多くの実際の本番エラーを引き起こすパターンをチェックすることで、Sentry バックエンドコードのバグを見つけます。
このスキルは、638 件の実際の本番イシュー(解決済み 393 件、未解決 220 件、無視 25 件)から得られたパターンをエンコードしたもので、65,000 人以上の影響を受けたユーザーにわたって 2,700 万件を超えるエラーイベントを生成しています。これらは理論上のリスクではありません。最も頻繁に出荷される実際のバグであり、解決済みイシューからの既知の修正方法があります。
スコープ
ユーザー、Warden、または現在のブランチ diff から提供されたコードをレビューします。ユーザーが対象を指定しない場合は、現在のブランチ diff をレビューします。変更されたハンクまたはファイルから開始し、動作を確認するために必要な範囲でのみ外側へ読み進めます。
- 変更されたコードを以下のパターンチェックと照らし合わせて分析します。
- 必要に応じて、
ReadとGrepを使用して最初の diff を超えてデータフローを追跡します。動作が確認できるまで、関数呼び出し、呼び出し元、シリアライザー、タスク、ORM の境界をたどります。 - HIGH と MEDIUM の確信度の検出結果のみを報告します。
| 確信度 | 基準 | アクション |
|---|---|---|
| HIGH | コードパスを追跡し、パターンが既知のバグクラスと一致することを確認済み | 修正方法とともに報告 |
| MEDIUM | パターンは存在するが、文脈によって緩和される可能性がある | 検証が必要として報告 |
| LOW | 理論上、または他の場所で緩和されている | 報告しない |
ステップ 1: コードを分類する
レビュー対象を判断し、関連するリファレンスを読み込みます。
| コードの種類 | 読み込むリファレンス |
|---|---|
ORM クエリ、モデル検索、.objects.get()、FK アクセス | references/missing-records.md |
| 型変換、None の処理、オプションの読み取り、シリアライザーの戻り値 | references/null-and-type-errors.md |
| データ入力のパース、フィールド長、リクエストボディ、伸長 | references/data-validation.md |
get_or_create、save()、一意制約、整数オーバーフロー | references/database-integrity.md |
| 統合 Webhook、外部 API 呼び出し、SentryApp フック | references/integration-errors.md |
| Dict のイテレーション、共有状態、同時アクセス | references/concurrency-bugs.md |
| Snuba クエリ、メトリクスサブスクリプション、検索フィルター | references/query-validation.md |
| リダイレクト URL、URL 構築、ルーティング | references/url-safety.md |
コードが複数のカテゴリにまたがる場合は、関連するすべてのリファレンスを読み込みます。
ステップ 2: 主要なバグパターンをチェックする
これらは、実際の本番データから得られた頻度と影響の組み合わせで並べられています。
チェック 1: メトリクスサブスクリプションクエリエラー -- 113 件のイシュー、3,035,640 イベント
対象データセットに存在しないタグや関数を参照するアラートおよびメトリクスサブスクリプション。これらは作成されると継続的に発火します。
危険な兆候:
- 検証せずにユーザー提供のクエリ文字列を使用して
SubscriptionDataで Snuba サブスクリプションを作成する - メトリクスデータセット上の p95/p99 関数で
transaction.durationを参照する(そこでは文字列型です) - カスタムタグ名(例:
customerType)が存在するか確認せずにフィルターディメンションとして使用する - データセットがしきい値パラメータをサポートしているか確認せずに
resolve_apdex_functionを呼び出す
安全なパターン:
- サブスクリプション作成前にクエリフィールドをデータセットスキーマと照合して検証する
_create_in_snuba呼び出しを try/exceptSubscriptionErrorでラップし、サブスクリプションを無効としてマークする- メトリクスサブスクリプションクエリを構築する前に
IncompatibleMetricsQueryチェックを使用する
チェック 2: レコードの欠落 / 古い参照 -- 81 件の問題、1,403,592 イベント
コードがレコードの存在を前提として Django モデルに対して .get() を呼び出していますが、そのレコードは削除、マージ、または一度も作成されていません。
危険なパターン:
DoesNotExistに対する try/except なしのModel.objects.get(id=some_id)- ワークフローエンジンにおける、削除処理なしの
Detector.objects.get(id=detector_id) - モニター / cron コンシューマーにおける
Environment.objects.get(name=env_name) - 請求タスクにおける
Subscription.objects.get(id=sub_id) - Snuba クエリ結果の ID を伴う
Group.objects.get()の使用 (グループは削除またはマージされている可能性があります) - 2 番目の
.get()が失敗する連鎖ルックアップ
安全なパターン:
- None チェックを伴う
Model.objects.filter(...).first() - グレースフルなフォールバック (404、スキップ、ログ) を返す
DoesNotExistの try/except .get()の前の Queryset.exists()チェック- API エンドポイントでは、
DoesNotExistには 404 を、検証エラーには 400 を返します。意図的に 500 を返すことを提案しないでください。
バグではない — 報告しないでください:
- インフラストラクチャの不変条件:
.get()がデプロイの前提条件を強制する場合 (例: 「シングル組織モードではデフォルト組織が存在しなければならない」)、クラッシュするのが正しい動作です — 500 はコードの欠陥ではなく、設定ミスを示します。 - 親による検証済み: エンドポイントの基底クラスがオブジェクトを検証している場合 (例:
OrganizationEndpointが組織を解決する)、真の競合や削除の時間枠が存在しない限り、関連レコードに対する.get()を報告しないでください。報告する前にエンドポイントの親クラスを確認してください。 - 設定のルックアップ: 必須の設定オブジェクトを読み込むコード (
get_default()、設定ベースのルックアップ) は、設定が誤っている場合にハードに失敗することが想定されています。
チェック 3: 検索クエリの検証 -- 57 件の問題、2,001,330 イベント
ユーザー提供またはサブスクリプションに保存されたクエリフィルターが、無効な値、削除されたイシュー、または未解決のタグを参照することによる InvalidSearchQuery。
危険なパターン:
- 削除または名前変更されたプロジェクトを参照する保存済みサブスクリプションからのショート ID を伴う
by_qualified_short_id_bulk()の呼び出し - ユーザー提供のイシューショート ID に対して
Group.objects.get()を呼び出す_issue_filter_converter - 対象データセットにタグが存在するかを確認せずに呼び出される
resolve_tag_key() - アラートルールのサブスクリプションからの未検証の
query文字列を Snuba に渡す
安全なパターン:
by_qualified_short_id_bulk()に渡す前にショート ID を検証する --Group.DoesNotExistを処理する_issue_filter_converterを try/except でラップし、無効なフィルターに対して空の結果を返す- データセットのスキーマに対してタグの存在を事前検証する
チェック 4: 値の検証エラー -- 45 件の問題、1,000,624 イベント
不十分な入力検証による ValueError: アンパックエラー、無効な enum 値、期待されるオブジェクトの欠落、および Pydantic/DRF の検証失敗。
危険なパターン:
- 結果が正確に 1 件存在すると仮定したアクションビルダーにおける
SentryAppInstallation.objects.get() DoesNotExistを処理せずに ID で検索するAlertRuleWorkflow- 区切り文字がより少ない可能性がある文字列に対するタプルのアンパック (
a, b, c = value.split(":")) ValueErrorを捕捉せずに行う整数 enum のルックアップ (例:DetectorPriorityLevel(value))
安全なパターン:
.get()の前に期待される件数を検証する:.filter()を使用して.count()を確認する- タプルのアンパックを try/except
ValueErrorでラップするか、事前に長さを検証する - ユーザー向けの enum 変換には
try: EnumClass(value) except ValueError:を使用する - API エンドポイントでは、
ValueErrorおよび検証失敗に対して 400 を返します。
チェック 5: 型エラー -- 28 件の問題、740,106 イベント
関数に誤った型が渡されています。イテラブルでないものに対する反復、無効な dict キー、オブジェクトが期待される箇所での None などです。
危険信号:
orjson.dumps(payload)で、payload の dict に文字列以外のキーが含まれる可能性があるlist(setting)で、settingが int である可能性がある (project.get_option()から取得)result["key"] = valueで、resultが None である可能性がある- DB/設定から取得した値が list ではなく int や None である可能性があるのに反復している
安全なパターン:
- 反復の前に型チェック:
list(value)の前にisinstance(value, (list, str)) - JSON シリアライズの前に dict のキーを検証: すべてのキーが文字列であることを確認する
- 添字代入の前に None ガード:
if result is not None: - 型チェックを伴う防御的なオプション読み取り
- API エンドポイントでは: ユーザー入力からの型の不一致に対して 400 を返す。
チェック 6: 内部 API リクエストエラー -- 71 件の問題、829,753 イベント
サービス間の内部 Sentry API 呼び出しによる ApiError で、多くの場合、古いサブスクリプションパラメータが原因です。
危険信号:
- メトリックアラートのチャート描画における
api.client.get()呼び出しで、400 レスポンスを処理していない - 古いサブスクリプションクエリ (削除されたタグ/メトリクスを参照) を転送する内部 API 呼び出し
incidents/charts.py内のfetch_metric_alert_events_timeseries()で ApiError を処理していない
安全なパターン:
- 内部 API 呼び出しを try/except
ApiErrorでラップし、穏当なフォールバックを返す - 内部 API リクエストを行う前にクエリパラメータを検証する
- チャート/可視化コードで 400/500 レスポンスを明示的に処理する
チェック 7: データベース制約違反 -- 22 件の問題、2,962,198 イベント
整数オーバーフロー、外部キー違反、一意制約違反による IntegrityError と DataError です。
危険信号:
- 上限を設けずに
times_seenカウンタをインクリメント (約 21 億で整数オーバーフロー) MonitorIncidentからの FK 制約を処理せずにMonitorCheckInレコードを削除- 下限が上限を超える可能性がある
GroupOpenPeriodの日付範囲に対するUPDATE - 一意制約を持つモデルに対する
save()でIntegrityErrorを処理していない
安全なパターン:
- 更新前に整数フィールドを上限で制限: 32 ビット int カラムには
min(value, 2_147_483_647) - 一括削除の前に
CASCADEを使用するか FK 制約を処理する - 保存前に日付範囲の境界を検証する
- try/except
IntegrityErrorでget()またはupdate_or_create()にフォールバックする
チェック 8: データのパースとデシリアライズ -- 19 件の問題、272,812 イベント
外部データや保存された圧縮データをパースする際の JSONDecodeError と ZstdError です。
危険信号:
JSONDecodeErrorを捕捉せずにjson.loads(response.body)- 破損したフレーム記述子を処理せずに
zstd.decompress(data) - 切り詰められている可能性がある VSTS/Azure DevOps Webhook ボディのパース
- API レスポンスが常に有効な JSON であると仮定する (HTML エラーページである可能性がある)
安全なパターン:
- JSON/圧縮を常に try/except でラップし、穏当なフォールバックを用意する
- パースの前に
content-typeヘッダーを確認する - ボディをパースする前にレスポンスステータスを検証する
チェック 9: キーアクセスの欠落 (KeyError) -- 25 件の問題、155,020 イベント
存在チェックなしで辞書のキーや HTTP ヘッダーにアクセスしています。
危険信号:
- Webhook ハンドラにおける
request.META["HTTP_X_GITLAB_TOKEN"](ヘッダーが存在しない可能性がある) - Bitbucket Webhook ハンドラにおける
request.META["HTTP_X_EVENT_KEY"] - イベントタイプが登録されているかを確認せずに
HANDLERS[event_type]で辞書のキーを検索 - 可変長の分割を伴うヘッダー値からのタプル展開
安全なパターン:
- None チェックを伴う
request.META.get("HTTP_X_GITLAB_TOKEN") - 未知のイベントタイプに対するフォールバックを伴う
HANDLERS.get(event_type) - 処理の前に Webhook ハンドラの先頭で必須ヘッダーを検証する
チェック 10: 並行性とランタイムのバグ -- 23 件の問題、38,443 イベント
イテレーション中の辞書の変更、共有された可変状態、および未実装のコードパス。
危険信号:
- 別のスレッドが変更している最中の
for key in self._dict:(RuntimeError) - 無制限に成長する共有
KafkaPublisher辞書への発行 - 別のスレッドでイテレーションされている辞書に対する
dict.pop()またはdict[key] = value - 新しい検索式タイプに対する
NotImplementedErrorハンドラーの欠如
安全なパターン:
- イテレーション前の
dict.copy() - 共有された可変状態には
threading.Lockを使用する - 機能を有効化する前にすべてのコードパスを実装する
- 変更が必要な場合の安全なイテレーションには
list(dict.keys())を使用する
チェック 11: ロジックの正しさ -- パターンベースではない
上記のすべての既知のパターンをチェックした後、変更されたコード自体について推論します:
- すべてのコードパスは正しい型を返しますか?
- 条件分岐のすべてのブランチが処理されていますか (特に
else/ デフォルトケース)? - 任意の入力 (None、空のリスト、0、空の文字列) が予期しない動作を引き起こす可能性はありますか?
- ループ、スライス、または範囲チェックに off-by-one エラーはありますか?
- このコードが並行して実行される場合、共有状態は保護されていますか?
バグを引き起こす特定の入力を追跡できる場合にのみ報告してください。理論上の懸念を報告しないでください。
バグではない — フラグを立てないでください:
- インフラストラクチャの不変条件を強制する
assertステートメント — Sentry はpython -Oで実行されないため、アサーションは常にアクティブです。違反された不変条件でクラッシュすることは意図的です。 - 投機的な入力の懸念 (例: 「この URL は長すぎる可能性がある」、「このヘッダーは不正な形式である可能性がある」) — ただし、入力が実際に検証されずにコードパスに到達することを示せる場合を除きます。報告する前に既存の検証 (ホストチェック、スキーマ検証、DRF シリアライザー) を確認してください。
チェックで潜在的な発見が得られなかった場合は、停止してゼロ件の発見を報告してください。レポートを埋めるために問題をでっち上げないでください。コードにこれらのパターンに一致するバグがない場合、空の結果が正しい出力です。
各コードの場所は、最も具体的に一致するパターンの下で一度だけ報告する必要があります。同じ行を複数のチェックの下でフラグを立てないでください。
ステップ 3: 発見の報告
各発見について、レビューハーネスが必要とする証拠を提供してください:
- 正確な場所
- 重大度と信頼度
- 具体的なトリガーとなる入力または状態
- 根本原因と結果
- 利用可能な場合の一致する本番環境の前例
- 具体的なコード修正、ハーネスがサポートしている場合は統一 diff として
修正提案には実際のコードを含める必要があります。修正としてコメントや docstring を提案しないでください。
API エンドポイントでの修正を提案する場合、適切な HTTP ステータスコードを使用してください (見つからない場合は 404、不正な入力の場合は 400、競合の場合は 409)。意図的に 500 を返すことを提案しないでください。
独自の出力形式を規定しないでください — レビューハーネスがレスポンス構造を制御します。