はじめに
Crashlytics のベロシティアラートが飛んできたとき、「これは対応が必要なクラッシュなのか」を判断するには、コンソールを開き、スタックトレースを読み、該当コードを探し、トレンドを確認する — という一連の作業が必要です。
従来、この対応にはいくつかの課題がありました:
- 検知の属人化: Crashlytics のメール通知を確認するルールが明文化されておらず、エンジニアのボランティアベースだった。見落としも発生していた
- 対応開始までのリードタイム: タイミングによっては担当者のアサインから始まるため、夜間や休日は特に初動が遅れていた
- 調査品質のばらつき: 担当者によって調査の深さや判断基準が異なり、一貫性がなかった
- 調査コスト: 1件あたり1時間程度のエンジニア工数がかかっていた
この課題に対して、Devin + GitHub Actions で Crashlytics アラートの検知から原因調査・インシデント判定までを自動化する仕組みを構築しました。これにより:
- 調査開始までのリードタイムが劇的に短縮: アラート検知から数分で自動的に調査が開始される。夜間・休日も関係なく稼働する
- 調査精度の安定: プロンプトで定義された手順に沿って毎回同じ品質で調査が行われる
- 一次調査のエンジニア工数ゼロ: 人手による確認・トリアージが不要になった
- 修正PRの自動作成: 原因が自明な場合は修正PRまで作成されるため、エンジニアはレビューから入れる
| 従来(手動) | 自動化後 | |
|---|---|---|
| 検知 | ボランティアベース・見落としあり | 15分毎に自動検知・24時間稼働 |
| リードタイム | 1〜2営業日(夜間・休日は翌営業日) | 数分で調査開始 |
| 調査品質 | 担当者依存でばらつき | プロンプト定義で一定品質 |
| エンジニア工数 | 約1時間/件 | 最終判断・レビューのみ |
前回の記事ではプラットフォーム変更の自動監視を紹介しましたが、今回は リアクティブな障害対応 の自動化です。前提として、複数アプリで共通基盤を共有する構成のため、「クラッシュの原因が共通基盤にあるのか個別アプリにあるのか」の切り分けが重要になります。
全体の流れ
処理は4段階に分かれています:
-
GAS(検知): Gmail に届く Crashlytics アラートメールを15分毎に検索し、Slack に通知すると同時に GitHub Actions をトリガー。
subject:"Crashlytics ベロシティ アラート" newer_than:1h is:unreadのようなクエリで、あえて1時間幅で検索しています。メール到達遅延や再送を吸収しつつ、is:unreadと処理後の既読化で重複を防止する設計です。メール本文から Crashlytics Issue URL を正規表現で抽出し、GitHub API のrepository_dispatchで URL と Slack スレッド情報を GitHub Actions に渡します。Cloud Functions 等でも実現できますが、既存のメール通知に乗せるだけで済む GAS を採用しました -
GitHub Actions(データ収集): Crashlytics REST API(
v1alpha)から5期間(1h/12h/24h/7d/30d)のトレンドデータとスタックトレースを収集し、自動で緊急度を判定。認証には OAuth2 refresh token を使い、issues・events・topIssuesエンドポイントを順に呼び出してデータを取得します -
Devin(調査): GitHub Actions が収集したデータをプロンプトテンプレートに埋め込み、Devin API(
POST /v1/sessions)にプロンプト全文を送信してセッションを作成。Devin はリポジトリを clone してコードベースの原因調査を実施し、結果を GitHub Issue に報告。LLM にスタックトレースを渡すだけの静的な解析とは異なり、実際のプロジェクト構造や依存関係を辿って調査できる点が Devin を採用した理由です。既にリポジトリ連携済みの環境があったため、追加のセットアップなしで導入できました - GitHub Actions(返信): Devin の完了をポーリングで検知し、Issue から判定結果を抽出して Slack スレッドに返信
Devin の完了検知はポーリング方式です。60秒間隔で Devin API(GET /v1/sessions/{id})のステータスを確認し、blocked(確認待ち)または stopped になったら完了とみなします。最大2時間でタイムアウトした場合は Slack に通知し、GitHub Issue へのリンクを提示して手動確認を促します。同時に複数のアラートが発生した場合は、GAS が Crashlytics URL ごとに個別のワークフローをトリガーするため、並行して調査が進みます。
実際の出力例
Slack 通知
1通のアラートに対して、Slack のスレッド内に3つのメッセージが時系列で投稿されます:
1. アラート通知(GAS)
*[要調査] Crashlytics ベロシティ アラート*
*件名:* ⚠️ com.example.myapp で新しいベロシティ アラートが発生しました
Crashlytics リンク:
• https://console.firebase.google.com/project/my-project/crashlytics/app/android:com.example.myapp/issues/abc123
2. 調査開始通知(GitHub Actions → スレッド返信)
🔍 Crashlytics 調査を開始しました
Devin セッション: https://app.devin.ai/sessions/xxx
GitHub Issue: https://github.com/.../issues/42
3. 判定結果(GitHub Actions → スレッド返信)
🟡経過観察
特定の端末・OS バージョンに依存する NullPointerException。
クラッシュ率は安定しており、次回リリースでの対応で十分。
詳細: https://github.com/.../issues/42
Slack を見るだけで「対応が必要か」がわかる設計です。詳細を知りたい場合のみ GitHub Issue を開きます。
GitHub Issue
以下は、Devin が実際に投稿した調査結果を匿名化したものです。外部SDK内部のネットワークエラーハンドリング不備によるクラッシュの調査例です。
### エスカレーション判定
- **判定**: 経過観察
- **理由**: クラッシュレートは30日間で約0.9件/hと安定しており急増傾向はない。
原因は外部SDK内部のネットワークエラーハンドリング不備であり、
ユーザーのネットワーク環境(DNS解決失敗)に起因する。
- **推奨アクション**: SDKの新バージョンリリース時にアップデートを検討。
短期的にはSDK初期化時のエラーハンドリング強化を検討。
### GitHub Actions 自動判定の検証
- **自動判定**: 🟡 安定
- **検証結果**: 妥当。クラッシュはユーザーのネットワーク環境依存であり、
コードバグやサーバー障害ではない。
### 原因分析
- **確信度**: 高
- **原因箇所**: 外部SDK・ライブラリ
- **直接原因**: 外部SDK内部で HTTP リクエスト(Retrofit + OkHttp経由)を
行う際、DNS解決に失敗し UnknownHostException が発生。SDK内部のコルーチン
例外処理に不備があり、例外がメインスレッドまで伝播して FATAL クラッシュとなる。
- **根本原因**: SDK内のコルーチンが CoroutineExceptionHandler なしで launch を
使用しており、ネットワーク例外がキャッチされない。
### 該当コード
外部SDK内部(スタックトレースから推定):
CoroutineScope(ioDispatcher) で launch(CoroutineExceptionHandler なし)
→ Retrofit API呼び出し → OkHttp が DNS解決に失敗
→ UnknownHostException がコルーチンから漏れて FATAL クラッシュ
アプリ側のSDK初期化コード:
SDK固有の例外のみ catch しており、ネットワーク例外は未捕捉。
ただしネットワーク例外はSDK内部の非同期コルーチンで発生するため、
このtry-catchでは捕捉不可能。
### 修正方針
外部SDKが原因のため、以下の対応を提案:
1. SDKバージョンアップでの解消可否を確認
2. アプリ側の回避策:
- 案1: グローバルな CoroutineExceptionHandler で FATAL クラッシュを防止
- 案2: SDK初期化前にネットワーク接続状態をチェック(推奨・影響範囲最小)
- 案3: SDK初期化をネットワーク接続確認後に遅延実行
Devin がスタックトレースとSDKの公開APIから内部の例外処理構造を推定し、3つの回避策を影響範囲付きで提案しています。
修正PRの自動作成
確信度が「高」かつ修正が局所的な場合、Devin は調査報告に加えて修正PRも自動作成します。PR本文には原因の発生フロー、影響範囲、設計判断の根拠、レビュー時の確認ポイントが含まれ、最終判断は人間のレビュアーに委ねる形です。
緊急度の自動判定
GitHub Actions が Crashlytics REST API から収集したトレンドデータをもとに、コードベースのロジックで自動判定します。レートは「該当 Issue のクラッシュ件数 / 時間」を件/h に正規化した値です。
| 判定 | 条件 |
|---|---|
| 🔴 急増 | 直近1hレート > 12h平均 x3、または 24hレート > 7d平均 x2 |
| 🟠 増加傾向 | 24hレート > 7d平均 x1.3 |
| 🟡 安定 | 急増でも増加でもない |
| 🔵 減少傾向 | 24hレート < 7d平均 x0.5 |
| 🟢 収束 | 直近24h = 0 かつ 7d > 0 |
| ⚪ データ不足 | 30日間 = 0 |
これらの閾値は、過去のクラッシュアラートの発生パターンをもとにチューニングした経験則です。特に「x3」「x2」といった倍率は、通常変動によるノイズをフィルタしつつ実際のインシデントを検知できるバランスを運用しながら調整しています。
Crashlytics のシグナル(SIGNAL_REGRESSED = 再発、SIGNAL_FRESH = 新規急増)が付いている場合は、安定以下の判定を自動的に「増加傾向」に引き上げます。
設定のポイント
プロンプト1ファイルで調査を制御
前回の記事で紹介したプラットフォーム監視と同様、この仕組みの核は 約250行のプロンプトファイル 1つです。Devin に「クラッシュ調査・インシデント判定エージェント」としての役割を与え、調査手順から報告フォーマットまでをすべてプロンプトで定義しています。
構造は2層に分かれます:
- 固定の構造: 調査ステップ(リポジトリ clone → スタックトレース照合 → 原因分類 → 緊急度判定)、判定基準、出力フォーマット、報告手順
- 可変のデータ: GitHub Actions がプレースホルダーに実データを埋め込む
prompt-template.md
├── 役割定義・制約事項
├── {{CRASH_INFO}} ← パッケージ名、エラータイプ、影響バージョン
├── {{URGENCY_DATA}} ← 5期間のトレンドデータ、シグナル
├── {{STACK_TRACE}} ← 例外情報 + スタックフレーム
├── {{REPOS}} ← 調査対象リポジトリ
├── 調査指示(Part 1: 原因調査 / Part 2: 緊急度判定)
├── 出力フォーマット
└── 報告方法(Issue 報告 → 対象リポジトリ Issue 作成 → 相互リンク)
例えば {{URGENCY_DATA}} には、以下のようなデータが埋め込まれます:
## 緊急度判定データ
自動判定: 🟡 安定
| 期間 | クラッシュ件数 | ユーザー数 | レート(件/h) |
|------|-------------|-----------|-------------|
| 直近1h | 1 | 1 | 1.00 |
| 直近12h | 8 | 6 | 0.67 |
| 直近24h | 18 | 14 | 0.75 |
| 直近7d | 152 | 98 | 0.90 |
| 直近30d | 643 | 412 | 0.89 |
シグナル: なし
{{REPOS}} には、パッケージ名から repos.json(テナント・リポジトリのマッピング情報)を検索して特定した調査対象リポジトリが埋め込まれます。共通基盤のテナントの場合はアプリリポジトリに加えてコアリポジトリも調査対象に含まれるため、Devin は両方を clone して原因箇所が共通基盤にあるのか個別アプリにあるのかを切り分けます。
GitHub Actions がプレースホルダーを置換して完成したプロンプトを Devin API に送信します。調査の精度を上げたい場合やフォーマットを変えたい場合は、このプロンプトファイルを編集するだけで済みます。プロンプトファイル自体もリポジトリで管理しており、変更は PR レビューを経てマージされるため、調査ロジックの変更履歴がコードと同様に追跡可能です。
権限管理
各コンポーネント間の認証情報(Crashlytics API の OAuth2 refresh token、Devin API token、Slack Bot Token、GitHub PAT)はすべて GitHub Actions のリポジトリシークレットで管理しています。Devin には対象リポジトリをワークスペース設定で連携しており、調査に必要なリポジトリのみにアクセスを限定しています。
二段階の緊急度判定
この仕組みの特徴的な設計は、緊急度判定を 2段階 で行うことです:
第1段階(GitHub Actions・データ駆動): Crashlytics REST API のトレンドデータから、クラッシュ率の時系列変化を算出して自動判定。Issue タイトルに [🔴急増] のようなラベルを付与。
第2段階(Devin・コード認識): 原因調査の結果を踏まえて最終判定。GitHub Actions の自動判定と異なる場合は上書き。
先ほどの実例では、GitHub Actions が 🟡安定 と判定し、Devin もコード調査の結果「外部SDK起因・ネットワーク環境依存で急増の兆候なし」として経過観察で一致しました。GitHub Actions のデータ駆動判定を、Devin がコードの文脈で裏付けた形です。
一方で、判定が異なるケースもあります。実際に、GitHub Actions が 🔴急増 と判定したクラッシュに対し、Devin がコード調査で「外部SDKの内部例外であり、アプリ側では対処不可」と判断して ✅対応不要 に修正したことがありました。逆に、レートは安定していても Devin がリグレッション(過去に修正済みのバグの再発)を検出すれば、緊急度を引き上げます。
GitHub Actions による機械的な判定で即座にトリアージし、Devin がコードを読んで文脈を加えるという役割分担です。
コスト
| 手動調査 | Devin + GitHub Actions | |
|---|---|---|
| 所要時間 | 30分〜1時間/件 | 10〜30分/件(自動) |
| リードタイム | 1〜2営業日(夜間・休日は翌営業日) | 30〜60分(24時間対応) |
| 1件あたりコスト | エンジニア工数 | 約2 ACU(数百円相当)+ GitHub Actions 数十円 |
GitHub Actions の内訳は、データ収集に約3分、Devin の完了待機に約30分です。GitHub Actions ランナーは GitHub-hosted Linux runner($0.008/分)を前提としています。Devin の待機中もランナーが稼働しますが、最大2時間のタイムアウトでも$1程度に収まります。
金銭コスト以上に大きいのは、一次調査の割り込みがなくなったことです。エンジニアが集中作業を中断してクラッシュ調査にコンテキストスイッチする必要がなくなり、人がやるのは最終判断と必要に応じたレビューだけになりました。
やってみてわかったこと
確信度ベースの行動制御が重要
Devin に修正 PR の作成を許可する条件を「確信度: 高」に限定しています:
■ 確信度の定義:
- 高: スタックトレースの該当箇所をコード上で特定でき、
発生条件と修正案をコード差分で説明できる
- 中: 該当箇所は特定できたが、発生条件が複数ありうる
- 低: 行番号の不整合・情報不足で原因を絞り込めない
■ PR 作成条件(すべて満たす場合のみ):
1. 確信度が「高」
2. 修正コードが明確
3. 局所的な変更(単一ファイル・数行〜十数行)
この設計に至ったのは、スタックトレースの情報が不十分なケース に遭遇したことがきっかけです。行番号がソースコードと一致せず、Devin が誤った箇所を「原因」として特定してしまいました。この経験から、スタックトレースの解析精度に不安がある場合は確信度の上限を「低」に制限し、PR 作成を禁止するルールをプロンプトに追加しました。
*AI エージェントに自律的な行動を許可する場合、「どこまで許可するか」の境界を確信度のような段階的な指標で制御することが重要だと思います。全面禁止でも全面許可でもなく、条件付きの許可が実用的でした。
フィードバックループをプロンプトに組み込む
プロンプトの最後に「プロセスフィードバック」セクションを設け、調査で問題があった場合に Devin が Issue に記録する仕組みにしています:
### プロセスフィードバック
- **不足データ**: 調査に必要だったが提供されなかったデータ
- **判断に迷った点**: プロンプトの指示が曖昧・矛盾していた箇所
- **改善提案**: 次回以降の精度向上のための提案
実際のフィードバック例として、「スタックトレースの行番号がソースコードと一致しない場合の対処方針が不明確」というものがありました。これを受けて、前述のスタックトレース解析精度に応じた確信度制限ルールをプロンプトに追加しています。
問題がなければスキップされるため、ノイズにはなりません。しかし問題があった場合は具体的な改善ポイントが記録されるため、プロンプトの継続的な改善に直結します。前回の記事で紹介した監視 URL の腐敗検知と同様、プロンプトの劣化を検知する仕組み をプロンプト自体に組み込むというアプローチです。
おわりに
この記事では、Devin + GitHub Actions による Crashlytics クラッシュアラートの自動調査パイプラインについて紹介しました。
1回目の記事では社内コードの変更分析、2回目では外部プラットフォームの監視、今回はクラッシュの自動調査と、AI エージェントの活用範囲を プロアクティブな監視 から リアクティブな障害対応 に広げてきました。いずれの仕組みも、要点はプロンプトファイル1つで、調査ロジックの変更や精度向上はプロンプトの編集で対応できるという共通の構成です。この記事で紹介したツール構成(Devin・GAS・GitHub Actions)は今後変わりうるものですが、二段階判定や確信度による行動制御といった AI エージェントへのガードレール設計 は、ツールが変わっても応用できるパターンだと考えています。
現状、確信度「高」に到達するケースは限定的です。外部SDK起因のクラッシュやリポジトリ構成が複雑なケースでは確信度が上がりにくいためです。今後はこの確信度を引き上げるためのプロンプト改善と、Crashlytics 以外のエラー監視(ANR、非クラッシュ例外)への対象拡大を進めていく予定です。
