Claude Media
Claude CodeにlogcatとANRトレースを読ませる — クラッシュと固まりの切り分け手順

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や計測ログで手元の再現に落としてから行います。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/headless
  2. code.claude.com/docs/en/permissions
  3. developer.android.com/topic/performance/vitals/anr
  4. developer.android.com/topic/performance/vitals/crash
  5. developer.android.com/tools/logcat
  6. developer.android.com/studio/debug/bug-report
この記事を共有:XはてブLinkedIn