CLAUDE LABEN
FORK — Claude Code 2.1.212で/forkの挙動が変わりました。会話を新しいバックグラウンドセッションへ複製し、作業を続けたまま並走できます。従来のセッション内サブエージェントは/subtaskに移りましたLIMITS — WebSearchの呼び出しがセッション単位で既定200回に制限されました。サブエージェントの起動も既定200回が上限で、暴走した検索・委譲のループを止められますMCPBG — 2分を超えるMCPツール呼び出しは自動的にバックグラウンドへ移り、セッションが固まらなくなりました。しきい値はCLAUDE_CODE_MCP_AUTO_BACKGROUND_MSで調整できますPLANFIX — プランモードがtouchやrmといったファイルを変更するBashコマンドを、許可プロンプトもcanUseToolコールバックも通さずに実行してしまう不具合が修正されましたSONNET5 — Claude Sonnet 5は導入価格として入力100万トークンあたり2ドル、出力10ドルで提供中です。8月31日を過ぎると3ドルと15ドルに戻りますIPO — Anthropicが早ければ10月の株式公開を視野に、引受銀行が投資家との面談を組み始めたと報じられていますFORK — Claude Code 2.1.212で/forkの挙動が変わりました。会話を新しいバックグラウンドセッションへ複製し、作業を続けたまま並走できます。従来のセッション内サブエージェントは/subtaskに移りましたLIMITS — WebSearchの呼び出しがセッション単位で既定200回に制限されました。サブエージェントの起動も既定200回が上限で、暴走した検索・委譲のループを止められますMCPBG — 2分を超えるMCPツール呼び出しは自動的にバックグラウンドへ移り、セッションが固まらなくなりました。しきい値はCLAUDE_CODE_MCP_AUTO_BACKGROUND_MSで調整できますPLANFIX — プランモードがtouchやrmといったファイルを変更するBashコマンドを、許可プロンプトもcanUseToolコールバックも通さずに実行してしまう不具合が修正されましたSONNET5 — Claude Sonnet 5は導入価格として入力100万トークンあたり2ドル、出力10ドルで提供中です。8月31日を過ぎると3ドルと15ドルに戻りますIPO — Anthropicが早ければ10月の株式公開を視野に、引受銀行が投資家との面談を組み始めたと報じられています
ブログ一覧

Crashlytics に並んだクラッシュを Claude と読む — 個人開発者の朝の障害対応ノート

crashlyticsandroiddebuggingclaude-workflowindie-developer

朝、コーヒーを淹れる前に Firebase Crashlytics を開くのが、ここ数年の習慣になっています。

2014年から壁紙アプリの個人開発を続けてきて、累計のダウンロードは5,000万を超えました。数字としては嬉しいのですが、ユーザーが増えるほどクラッシュの絶対数も増えます。そして個人開発である以上、夜中に積み上がったスタックトレースを読むのも、原因を仮説立てるのも、修正を当てるのも、すべて私一人の仕事です。

長いあいだ、この朝の時間がいちばん心細いものでした。見慣れないネイティブクラッシュがずらりと並んでいるのを見て、「これはどこから来ているんだろう」と途方に暮れる。誰かに「これ、何だと思う?」と聞ける相手がいないのです。Claude を障害対応の相棒に据えてから、その心細さがずいぶん軽くなりました。今日はその実際のやり方を綴ってみます。

スタックトレースは「読む」のではなく「一緒に読む」

Crashlytics のクラッシュレポートは、慣れていても情報量が多すぎます。スレッド一覧、ネイティブフレーム、難読化されたメソッド名、影響を受けたOSバージョンと端末。私はこれを全部頭に入れてから原因を考える、という読み方を長くしていました。

今は違います。気になるクラッシュを開いたら、スタックトレースの該当部分をそのまま Claude に貼り付けて、こう聞きます。

このスタックトレースから読み取れる一次仮説を3つ、確からしい順に挙げてください。影響範囲を絞り込むために次に確認すべき情報も教えてください。

ポイントは「答えを出して」ではなく「仮説を確からしい順に」と頼むことです。クラッシュの原因は一つに見えて複数の要因が絡むことが多く、最初から断定されると視野が狭くなります。確度の順位づけと「次に見るべき情報」を一緒にもらうことで、私自身の調査の手順が組み立てられていきます。

ケース1:RecyclerView の IndexOutOfBoundsException

2026年5月、Android 版の壁紙アプリを v2.0.0 にアップデートした直後のことでした。リリースから28日のあいだに、50人以上のユーザーで IndexOutOfBoundsExceptionRecyclerView 周辺で発生していました。再現手順が分からず、自分の端末では一度も起きません。

スタックトレースを貼り付けて相談すると、Claude は「リスト更新中にバックグラウンドスレッドでデータソースが書き換えられ、RecyclerView が古いインデックスを参照している可能性が高い」と指摘しました。私のコードはこうなっていました。

// Before:アダプターが内部リストを直接参照していた
class WallpaperAdapter(
    private val items: MutableList<Wallpaper>
) : RecyclerView.Adapter<WallpaperViewHolder>() {
 
    override fun onBindViewHolder(holder: WallpaperViewHolder, position: Int) {
        // items が別スレッドで書き換わると position がずれる
        holder.bind(items[position])
    }
 
    override fun getItemCount(): Int = items.size
}

問題は、items という可変リストを画面の外(ダウンロード完了コールバックなど)から書き換えていたことでした。notifyDataSetChanged() を呼ぶ前にリストの中身が変わると、getItemCount()onBindViewHolder() のあいだでインデックスがずれてしまいます。

Claude と相談しながら、更新のたびに防御的コピーを渡す形に書き直しました。

// After:更新は必ず不変リストの差し替えで行う
class WallpaperAdapter : RecyclerView.Adapter<WallpaperViewHolder>() {
 
    private var items: List<Wallpaper> = emptyList()
 
    // 外部からはこのメソッド経由でしか更新できない
    fun submit(newItems: List<Wallpaper>) {
        // 防御的コピーを取ってから差し替える
        items = newItems.toList()
        notifyDataSetChanged()
    }
 
    override fun onBindViewHolder(holder: WallpaperViewHolder, position: Int) {
        holder.bind(items[position])
    }
 
    override fun getItemCount(): Int = items.size
}

たったこれだけの変更ですが、submit() を唯一の更新口にしたことで、リストの差し替えが原子的になりました。v2.1.0 を段階公開した結果、このクラッシュは Crashlytics から完全に消えました。再現できないクラッシュを「再現しようとする」のではなく、「再現しても壊れない構造にする」という発想の転換は、Claude との対話の中で言語化できたものです。

ケース2:1行で消えた NoClassDefFoundError

もう一つ、印象に残っている朝があります。画像読み込みライブラリの Glide を 5.0.5 に上げ、Android Gradle Plugin を 9 系に更新したあと、特定の古い端末でだけ起動直後にクラッシュするようになりました。スタックトレースには java.lang.NoClassDefFoundErrorjava.util.function.Supplier の文字が並んでいました。

影響を受けていたのは Android 6.0.1 のユーザーに集中していました。これを Claude に伝えると、「Supplier は Java 8 で追加された API で、API レベル24未満の端末には存在しません。ライブラリが内部で Java 8 のクラスを参照しているのに、デスガーリングが効いていないのではないか」という仮説が返ってきました。

確認すると、まさにその通りでした。修正は build.gradle にたった1行です。

android {
    compileOptions {
        // Java 8+ の API を古い端末向けにデスガーリングする
        coreLibraryDesugaringEnabled true
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}
 
dependencies {
    coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.0.4"
}

この1行を加えただけで、Android 6.0.1 のユーザーで起きていたクラッシュがすべて消えました。原因にたどり着くまでの時間が、一人で公式ドキュメントを掘る場合と比べて圧倒的に短かったのが、このとき強く印象に残っています。Claude は答えを断定したのではなく、「OSバージョンの偏り」という私が見落としていた手がかりを起点に、正しい問いを立て直してくれたのです。

障害対応で私が守っているルール

何でも貼り付けて聞けば解決する、という話ではありません。一人での障害対応に Claude を組み込む中で、私なりに守っているルールがいくつかあります。

一つ目は、必ず影響範囲の数値とセットで相談することです。「何人のユーザーで」「どのOSバージョンで」「いつのリリース以降か」を添えるだけで、仮説の精度がまるで変わります。OSバージョンの偏りが NoClassDefFoundError の鍵になったように、数値そのものが手がかりです。

二つ目は、修正を当てたら段階公開で確かめることです。私は壁紙アプリの更新を 5% → 25% → 50% → 100% の順で公開し、Crash-free users が 99.7% を下回らないか、ANR が 0.20% を超えないかを見ながら次の段階に進めています。Claude の提案であっても、本番のユーザーで検証するまでは「仮説」のままだと考えています。

三つ目は、最終的な判断は自分で持つことです。コードを書くのも、世に出す責任を負うのも私です。Claude は素晴らしい相談相手ですが、デプロイボタンを押すのは私自身でなければならないと思っています。

Claude Code を使った日々のワークフローについてはClaude Code が動かないときの対処チェックリストも合わせて読んでいただけると、環境側の切り分けの参考になるかもしれません。収益とクラッシュ率を一つの画面で追いたい方にはClaude Code でモバイルアプリ収益ダッシュボードを作る記事もおすすめです。

明日の朝、最初に試してほしいこと

もし今、原因の分からないクラッシュを抱えているなら、明日 Crashlytics を開いたときに、スタックトレースと一緒に「影響を受けたOSバージョンの分布」を Claude に貼り付けてみてください。原因そのものより先に、見落としていた偏りが見つかることがあります。

一人で開発を続けていると、誰にも相談できない朝が必ずあります。私自身まだ手探りの部分も多いのですが、同じように一人で障害に向き合っている方の、ほんの少しの心細さが減るなら嬉しいです。お読みいただきありがとうございました。