Claude CodeにlogcatとANRトレースを読ませる — クラッシュと固まりの切り分け手順
Androidのクラッシュはcrashバッファ、ANRは/data/anrのトレースに残ります。claude -pに標準入力で渡し、例外の型やメインスレッドの止まり先を絞る手順をまとめます。
AndroidアプリのクラッシュとANR(Application Not Responding)は、ログの残り方が違います。クラッシュはlogcatのcrashバッファに例外が出ます。ANRは端末の /data/anr にトレースが残ります。
Claude Codeの非対話モード(claude -p)は標準入力を読めるので、どちらもパイプで渡せば原因の候補を絞れます。ここでは、ログの取り方、渡し方、プロンプトの書き方、権限の絞り方を順に並べます。
クラッシュとANRでは見る場所が違う
最初に決めるのは、症状がどちらかです。アプリが落ちたのか、画面が固まってANRダイアログが出たのかで、取るログが変わります。
| 症状 | ログの場所 | 取り方 | 最初に見る行 |
|---|---|---|---|
| アプリが落ちる | ログの場所logcatのcrashバッファ | 取り方adb logcat -b crash -d | 最初に見る行FATAL EXCEPTION: main と例外の型 |
| 画面が固まる・ANRダイアログ | ログの場所端末の /data/anr | 取り方adb pull か adb bugreport | 最初に見る行メインスレッドのスタック |
クラッシュのstack traceは、Android公式の例では AndroidRuntime: FATAL EXCEPTION: main で始まり、次の行にプロセス名とPID、その次に java.lang.NullPointerException のような例外の型が並びます。例外の型が原因への強い手がかりになり、スタックの上から数行が投げた場所を示します。
ANRのほうは、アプリのメインスレッドが長く塞がれたときに起きます。入力イベントに5秒以内に応答できない場合が代表例です。Service、BroadcastReceiver、JobServiceにも別々の制限時間があります。
ANRの典型的な原因5つ
メインスレッドのI/O
ディスクやデータベースの読み書きをUIスレッドで待っている状態です。
長い計算
並べ替えや変換を、再コンポーズのたびにメインスレッドで回している状態です。
同期のbinder呼び出し
別プロセスへの呼び出しが長引き、その返事を待っている状態です。
synchronizedの待ち
別スレッドが握る長いロックを、メインスレッドが待っている状態です。
デッドロック
ただ待つのではなく、互いのロックを待ち合って進まない状態です。
この5つは公式の「ANRを診断する」節のパターンです。トレースを読ませるときは、この分類に当てはめさせると答えが揃います。
logcatを絞ってからClaude Codeに渡す
logcatは全体をそのまま渡さず、先に絞ります。claude -p が受け取る標準入力は10MBまでで、超えるとエラー終了するためです。トークンの面でも、関係のない行は少ないほうが読みやすくなります。
# crashバッファだけを取り出して終了する
adb logcat -b crash -d > crash.log
# 特定タグだけ(*:S で他のタグを黙らせる)
adb logcat -d AndroidRuntime:E *:S > crash-filtered.logタグと優先度で絞る書式は タグ:優先度 で、*:S を最後に置くと指定したタグ以外が出なくなります。-b でcrashやsystemなどのバッファを選べます。-d は公式の例にも adb logcat -b all -v color -d として載っています。使えるオプションは端末のOSによって変わるため、手元の端末では adb logcat --help で確かめてください。
絞ったログは標準入力でそのまま渡します。
cat crash.log | claude -p \
'この logcat のクラッシュを分析して。例外の型、投げた場所、
考えられる原因を順に挙げ、根拠にした行をそのまま引用して'この渡し方は、公式のビルドログの例(cat build-error.txt | claude -p '...')と同じ形です。標準入力で渡したログはClaudeがBashで読む必要がないため、Bashの権限も要りません。
ANRのトレースからメインスレッドの止まり先を探す
ANRはlogcatだけでは足りません。トレースファイルを端末から取り出します。
adb root
adb shell ls /data/anr
adb pull /data/anr/<ファイル名> ./anr/公式の説明では、古いOSでは /data/anr/traces.txt の1ファイルで、新しいOSでは /data/anr/anr_* に複数のファイルが残ります。取り出しにはadbのrootが要ります。実機でrootが取れない場合は、端末の開発者向けオプションの「バグレポートを取得」か adb bugreport でzipを作ります。バグレポートにはdumpsysの出力、dumpstateのエラーログ、logcatのシステムメッセージが入っています。
トレースを渡すときは、プロジェクトのディレクトリで claude -p を動かし、ソースを読む権限だけを足します。
claude -p \
'ANR のトレースです。次の順で答えて。
1. メインスレッドのスタックを抜き出す
2. 止まっているのが I/O、計算、binder、ロック待ち、
デッドロックのどれに当たるか
3. ロック待ちなら、そのロックを握る別スレッドのスタック
4. スタック中のアプリのクラスを、プロジェクト内で探して
該当行を示す' \
--allowedTools "Read,Grep" < anr/anr_2026-10-27-10-12-33--allowedTools に Read と Grep だけを置いたので、ソースの参照は止まらずに進みます。-p でManualモードの実行中に承認が必要な呼び出しが出ると、承認する手段がなければ拒否される仕様です。そのため、使わせたいツールは先に並べておきます。
プロンプトの3番が肝です。メインスレッドが止まっているのは症状で、原因は別スレッドにあることが多いからです。公式も、同期ブロックの待ちとデッドロックを別々のパターンとして挙げています。
落とし穴
スタックの一番上だけでは原因と言い切れない
メインスレッドのスタックが示すのは、トレースを取った瞬間に何を待っていたかです。長い処理の途中でたまたま止まっているだけの場合もあります。Claudeに「原因」と書かせず「候補と、それを裏づけるスタックの行」を書かせると、読み違いに気づきやすくなります。
ANRの発火条件もプロンプトに含める
同じANRでも、発火した条件で疑う場所が変わります。トレースの冒頭に出る理由と合わせて、次の対応表をプロンプトに添えると、Claudeが見るべきクラスを選びやすくなります。
| 発火条件 | 時間の目安 | 疑う場所 |
|---|---|---|
| 入力イベントに応答しない | 時間の目安5秒 | 疑う場所Activityのメインスレッド処理 |
| Serviceの起動処理が終わらない | 時間の目安数秒 | 疑う場所onCreate / onStartCommand / onBind |
startForegroundService 後に startForeground を呼ばない | 時間の目安5秒 | 疑う場所Serviceの開始直後の処理 |
| BroadcastReceiverが終わらない | 時間の目安フォアグラウンドで5秒 | 疑う場所onReceive、goAsync の finish 呼び忘れ |
| JobServiceが戻らない | 時間の目安数秒 | 疑う場所onStartJob / onStopJob |
バックグラウンドのANRは、端末の開発者向けオプションで「すべてのANRを表示」を有効にしないとダイアログが出ません。ダイアログが出ていなくても、遅いBroadcastReceiverがANRの原因になっている場合があります。ユーザーの報告が「たまに重い」程度でも、トレースが /data/anr に残っていないかを見る価値があります。
Android 11(APIレベル30)以降では、ApplicationExitInfo でアプリの終了理由を取得でき、ANRもその対象です。端末が手元になくても、アプリ側で終了理由を記録しておけば、ログを後からClaudeに渡せます。
結果をJSONで受けて複数のトレースを回す
トレースが複数あるなら、出力をJSONスキーマで固定すると、一覧にしやすくなります。--output-format json と --json-schema を併用すると、結果が structured_output に入ります。
for f in anr/anr_*; do
echo "== $f"
claude --bare -p 'ANR のトレースを分類して' \
--allowedTools "Read,Grep" \
--output-format json \
--json-schema '{"type":"object","properties":{"pattern":{"type":"string","enum":["io","compute","binder","lock","deadlock","unknown"]},"mainThreadFrame":{"type":"string"},"evidence":{"type":"array","items":{"type":"string"}}},"required":["pattern","mainThreadFrame"]}' \
< "$f" | jq -c '.structured_output'
done--bare はCLAUDE.mdやフック、MCPサーバーの自動読み込みを飛ばす指定で、どのマシンでも同じ結果にしたいスクリプト向けです。サブスクリプションのログインを使わないため、ANTHROPIC_API_KEY の設定が必要です。プロジェクトのCLAUDE.mdにAndroidの規約を書いてあり、それを読ませたいなら --bare を外します。
出力に structured_output が無い場合、有効な出力が得られなかったということです。jq に渡す前に、フィールドがあるかを確かめる処理を入れておくと、ループが途中で空の行を出さずに済みます。
adbをClaude Codeに直接叩かせるときの権限
ログの取得まで任せたい場合は、adb をBashの許可ルールで絞ります。Bash(adb logcat -d *) のように、コマンドの先頭部分を書き、* はサブコマンドの後ろに置きます。
{
"permissions": {
"allow": [
"Bash(adb logcat -d *)",
"Bash(adb pull /data/anr/*)",
"Read",
"Grep"
],
"deny": [
"Bash(adb uninstall *)",
"Bash(adb shell pm clear *)"
]
}
}-d を付けない adb logcat は終わらずに流れ続けるため、許可ルールにも -d を入れておくと、止まらない呼び出しを避けられます。denyはallowより強く、denyに当たる呼び出しはallowに細かい例外を書いても通りません。誰も承認できない定期実行では、--permission-prompts none が使えます。承認が要る呼び出しは拒否され、Claudeは再試行しません。このフラグはv2.1.259以降です。
読ませる前と読ませたあとの注意
ログを渡す前後に見る点
個人情報の混入
logcatにはアプリが出力した値が含まれます。メールアドレスやトークンを出すログがあれば、渡す前に消すか、該当行を除きます。
ビルドとトレースの対応
トレースを取ったビルドと、Claudeに読ませるソースの版が違うと、行番号がずれます。Android StudioのAnalyze Stacktraceでも、同じ版のソースを開くよう注意書きがあります。
裏づけの再現
Claudeの結論は仮説です。StrictModeでメインスレッドのI/Oを検出する、該当処理に時間を計るログを入れるなど、手元で再現できる確認に落としてから直します。
Replicant MCPなどとの使い分け
adb経由の操作を会話の中で完結させたいなら、MCPサーバーを使う方法もあります。Replicant MCPをClaude Codeにつないでビルドと画面操作を任せるの手順では、adb-logcat ツールでパッケージやタグ、レベルを指定してログを絞れます。端末の画面を操作しながら再現させたい場合は、ClaudeにAndroid-MCPをつないでAndroid端末を操作する手順も候補です。
この記事の方法は、すでに手元にあるログやトレースを後から読ませる経路です。ユーザーから送られたbug reportや、CIで保存したlogcatのように、端末が手元にない状況でも使えます。claude -p の権限やオプションの全体像は、claude -pオプションとはにあります。
まとめ
ログを取る場所は、クラッシュならcrashバッファ、ANRなら /data/anr かバグレポートです。どちらも先に絞って標準入力で渡し、ANRでは5つのパターンに当てはめさせて、ロック待ちなら握っているスレッドまで追わせます。
Claudeに任せるのは候補と該当行の抽出までです。原因の確定は、StrictModeや計測ログで手元の再現に落としてから行います。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。