【QAチーム ブログリレー1日目】
こんにちは。マルチデバイスチームQAエンジニアの前川です。
新国立のテート展のダミアン・ハースト、久しぶりにホルマリン漬け来るか!の期待に対しての無難なオフィス机の展示にちょっぴり落胆した春先です。
最近LAでクラブよりも午前中のコーヒーパーティが流行っているらしいです。夜&酒の脳コンディションよりもシラフで冴えた頭への社交シフトがビジネスやクリエイティブ層で強まっている。密度と精度、スピードが重視されるAIの普及が、コミュニケーション指向へも影響していると取れなくもないと思えます。

- はじめに
- 消えることのない課題
- Firebase MCPを用いたワークフロー実装
- Cloud Functions /Firestoreを用いない実装
- run.py
- 解析精度
- まとめ
- We are hiring!
はじめに
テスト設計&実行のAI Agent活用がQAチームでも加速度的に進んでいます。
・テスト設計&実行の自動化 -> QA工程の短縮化 -> リリース数の増加
テスト生産性が上がり、リリースのインターバルは短くなる、とその先の運用品質、運用監視がQAとしてより一層留意される流れになってきます。
テストプロセスから少し目線を変えて、運用プロセスでのAI有用性の観点でマルチデバイスチームの模索を紹介できればと思います。
消えることのない課題
モバイルアプリにつきもののCrash。
クライアント側起因に加えてOSライブラリ依存、ユーザー環境、予想外のユースケースなどでエラーはコンスタントに積み重なっていきます。
サービス横断のモバイルアプリはバックエンド、BFF、フロントとエラー因数も多いため、切り分けもなかなかに手こずります。
- レポート注視:Firebase Crashlytics、クライアント側&ライブラリ起因でPriorityたかひく混成で日々発生
- 気づき:warnレベルを含めた単純なレポートの自動通知は誰かが気づいたときに対応する属人的な運用になりがち
- 解析:スタックトレースを追う、エラー解析の因数は多い
このCrash監視&修正の一連の運用でClaude Codeの活用は2軸になろうかと思います。
- レポート注視から解析までのワークフローの自動化
- 解析精度自体の向上
Firebase MCPを用いたワークフロー実装
まずはCrashlyticsのレポートを拾い、解析し、重症度のPriorityをつけて、Slack通知する仕組み化、です。
以下、マルチデバイスチームのエンジニア小林さんに実装いただいた経緯です。
Firebase MCP はご存知のようにAI AgentがFirebaseの諸機能にアクセスできる公式サーバです。
firebase-tools(Firebase CLI)はFirebaseの各種機能のツールセットとして今回のケースではカスタムログやキー情報など解析&修正提案に必要なコンテキストを取得します。
Claude Code <---> Firebase MCP <---> firebase-tools <---> Crashlyticsサーバー
実装方法ですが、当初はGeminiの勧めもあってまず検索性や分析精度を踏まえてCloud Functionsの利用を検討しました。
Cloud Functionsでcrashイベントを受け取ってcrash情報をFirestoreに保存
Gitlab CIの定期実行でjobを起動し、Firestoreにある未解析なクラッシュ情報を探して処理
以下、Firebase側とci側の実装案です。
1.Firebase側(書き込み):
クラッシュ発生時、Cloud FunctionsがFirestoreの crash_queue コレクションに「未解析(pending)」として情報を書き込む。 Firebaseのアラートを検知して情報をキュー(待ち行列)として保存。
2.GitLab CI側:
定期実行ジョブでGitLab CIがFirestoreをチェック。
pending なデータがあればそのIDを使ってClaude解析を実行し、終わったら completed に更新する。
オンプレのGitLab Runnerは内側から外側(Google Cloud)へ通信するため、特別なネットワーク設定なしでFirestoreのデータを取得できる

しかし、
- functionsのデプロイには従量課金のblazeプランへの移行が必要な点
- テスト運用段階ではシンプルな実行が望ましい
最終的にまずはGitLab CIのみで完結させる方法で実装でいくことにしました。
Cloud Functions /Firestoreを用いない実装
テスト運用はエムスリーのサービスの軸であるポータルアプリ”m3.comアプリ”のiOS版で実装してもらいました。
(m3.comアプリの設計について気になる方はこちらをご覧ください。)
Prompt
は以下です。まずは3時間の定期実行にしました。 重症度は以下4レベル判定になっています。CriticalとHighを拾って深掘りする運用の前提です。
- Critical: アプリ起動不能・データ損失など致命的
- High: 主要機能が使えない・多数ユーザーに影響
- Medium: 一部機能に影響・回避策あり
- Low: 軽微・再現頻度低
あなたはiOSアプリ「m3comapp-ios」のCrashlytics監視エージェントです。
Firebase MCPツールを使用して以下の手順でクラッシュを調査し、結果をJSONで出力してください。
## 調査手順
1. Firebase MCPツールを使用して、以下のプロジェクトのCrashlyticsから条件を満たすクラッシュ情報を取得する
- プロジェクトID: $FIREBASE_PROJECT_ID
- アプリID: $FIREBASE_APP_ID
- 条件: firstSeenが過去3時間以内
2. 該当Issueが0件の場合は `[]` のみ出力して終了する
3. 該当Issueが1件以上ある場合、各Issueについて以下を行う:
a. スタックトレースを取得する
b. スタックトレースのファイルパスを元に、このリポジトリ内の関連Swiftファイルを読み込む
c. 原因の分析結果と修正方針を日本語で推定・提案する
d. 重篤度を評価する (Critical / High / Medium / Low)
- Critical: アプリ起動不能・データ損失など致命的
- High: 主要機能が使えない・多数ユーザーに影響
- Medium: 一部機能に影響・回避策あり
- Low: 軽微・再現頻度低
## 出力形式
必ずJSONのみ出力すること。前後に説明文・マークダウンのコードブロック (``` など) を含めないこと。
出力をJSONとしてパースするので、失敗してしまいます。
出力するJSONの構造 (このテキスト自体は出力しない):
[
{
"issue_id": "CrashlyticsのIssue ID",
"title": "クラッシュのタイトル",
"severity": "High",
"first_occurred_at": "ISO8601形式の日時",
"occurrences": 0,
"affected_users": 0,
"analysis_result": "原因の分析結果と修正方針(日本語、マークダウン記法、JSONのエスケープルールに従うこと)",
"firebase_console_url": "FirebaseコンソールのIssue URL"
}
]
run.py
実行ファイルは以下です。
MCP設定ファイル生成のコンテンキストマネージャー
@contextlib.contextmanager def _mcp_config_file(): """MCP 設定ファイルを生成するコンテキストマネージャー。 iOS プロジェクトは Crashlytics 依存関係が自動検出されないため --only crashlytics が必須。 """ mcp_server = { "command": "npx", "args": ["-y", "firebase-tools@latest", "mcp", "--only", "crashlytics"], } config = {"mcpServers": {"firebase": mcp_server}} config_path = None try: with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f: json.dump(config, f) config_path = f.name yield config_path finally: if config_path: Path(config_path).unlink(missing_ok=True)
Claude呼び出し
subprocess.run()でclaudeを呼び出し、必要な情報を渡します。
許可ツールはread-onlyのもののみに制限して安全に実行できるよう設定。
- プロンプト (-p prompt): prompt.md から読み込んだ内容(Firebase プロジェクトIDとアプリIDを埋め込み)
- MCP設定 (--mcp-config): Firebase Crashlytics MCPサーバーの設定
- 許可ツール (--allowedTools): Read, Grep, Glob + Firebase Crashlytics関連のMCPツール
def run_claude() -> str: """claude CLI を非インタラクティブ実行してクラッシュ分析結果を返す。""" allowed_tools = [ "Read", "Grep", "Glob", "mcp__firebase__firebase_get_environment", "mcp__firebase__crashlytics_batch_get_events", "mcp__firebase__crashlytics_get_issue", "mcp__firebase__crashlytics_list_events", "mcp__firebase__crashlytics_get_report", "mcp__firebase__crashlytics_list_notes", ] prompt = build_prompt() with _mcp_config_file() as mcp_config_path: try: result = subprocess.run( [ "claude", "-p", prompt, "--mcp-config", mcp_config_path, "--allowedTools", ",".join(allowed_tools), ], capture_output=True, text=True, cwd=PROJECT_DIR, env=os.environ.copy(), timeout=1800, ) except subprocess.TimeoutExpired: print("claude 実行エラー: タイムアウト", file=sys.stderr) sys.exit(1) if result.returncode != 0: print(f"claude 実行エラー:\n{result.stderr}", file=sys.stderr) sys.exit(1) return result.stdout
Claudeは解析結果をJSONで返します。
私はテキスト派ですが、slack_notify通知は重要度でアイコン付けてeyecatchを高める。
SEVERITY_EMOJI = {
"Critical": "🚨",
"High": "🔴",
"Medium": "🟡",
"Low": "🟢",
}
通知サンプル

フロー改善の感触
Slackを注視するというアクションはもちろん属人として残りますが、
能動的に見にいき --> Claudeで解析 --> 重要性を含めてEng相談を投げる
という手動の流れから、まずはHighの通知に留意して、必要に応じて再解析で深掘りする、という初動のカットのメリットが出ます。
解析精度
ここでAI活用の2軸目:解析性能ですが、自身での感触もチームエンジニアの見解としても、やはり一般的な評価のラインに落ち着きそうです。
- 頻度やOSなどメタデータ含めた初動の時短には有効
- 修正実装の提案はドラフトの検討材料として留める
ポジティブな点
- スタックトレースからシステムライブラリのCrashを短時間で解析してくれる
- OSライブラリやSDKの既知の不具合を即座に指摘できる
不足点:修正提案の精度
- 当然ながらスタックトレースで解析が進むため、ネットワーク境界の問題だとサーバ応答との切り分け以前にクライアント起因に寄せがち、無理やりな解決をちょいちょい生み出しがち
- カスタムキー、カスタムログが適切に取れていないと推測の精度が格段に落ちる
まとめ
まずは運用を始めた段階ですが、今後の改善としては、
- 増加傾向の判定:ベロシティアラートのユーザ数・セッション数あたりの発生頻度をチェックし、同一バージョンにおいて直近24時間と前日比較でn%以上の増加
- 絶対数での閾値
などの判定要素の追加に余地がありそうです。
またfirebase-toolsの解析精度については先述のように
- アプリ実装側のカスタムキー、カスタムログを適切に仕込む
- サーバレスポンスなどのメタデータ、ロジックなど背景となるコンテキストを渡して精度をあげる
でいかに改善が得られるのか、試行錯誤が必要そうです。
We are hiring!
マルチデバイスチームではOSや採用技術を問わないモバイルアプリ開発&検証、そして運用フェーズにも興味のあるQAエンジニアを絶賛募集しています。 興味がありましたらぜひご応募ください!