●FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtask●LIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its own●MCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS●PLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callback●SONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15●IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October●FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtask●LIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its own●MCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS●PLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callback●SONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15●IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October
Automating iOS Crashlytics Triage with Claude Code — A Production Pipeline from dSYM Symbolication to Draft PR
How I rebuilt iOS crash triage at our 50M+ download app studio: Firebase Crashlytics issues flow into a Cloud Functions + Claude Code pipeline that handles dSYM symbolication, blast-radius estimation, and a draft fix PR in under 90 seconds. Real numbers, real code, real lessons.
It was nearly midnight in April 2026 when three new crashes hit our Slack from Firebase Crashlytics for the iOS build of Beautiful HD Wallpapers. The stack traces were unsymbolicated — just <redacted> and 0x0000000104a3b2c4 repeating. There was no way to act on them from a phone. Our process at the time required me to open Xcode on a Mac, pull the dSYM, walk git blame, file an internal issue, and revisit it the next morning. MTTD (Mean Time to Detect) sat around 2 hours 30 minutes; MTTR hovered at nine hours.
The pipeline I'm describing here is what replaced that loop. As one person running a 50M+ download app catalog alongside an art practice and four technical blogs, the cost of doing this work by hand at midnight had become untenable. After three weeks of production use and 67 issues processed, I want to share the architecture, the code, and the numbers — three views of the same system — so you can adapt it without making the mistakes I made in week one.
Why the Crashlytics dashboard alone stops scaling
Crashlytics gives you the basics: crash counts, affected users, the first version a crash appeared in, the device distribution. For most of the past decade that has been enough. But at our current scale — roughly 14 titles, 15 to 20 fresh issues per week including non-fatals — the bottleneck is no longer "seeing" issues. It is deciding which ones deserve a midnight response and which are existing OS bugs, ad-SDK internals we can't touch, or recurrences of known issues already on the backlog.
Crashlytics auto-clusters identical stacks, but the question we actually care about is contextual: does this stack correlate with the SwiftUI migration shipped in v2.3.1? Has it surged in the last 24 hours? Is it materially affecting revenue? Answering those requires cross-referencing GitHub history, App Store Connect financials, and an internal issue log. Claude Code is unusually good at exactly that kind of cross-document reasoning when given a tight prompt and limited tools.
There is also something I'll admit honestly: I grew up with both grandfathers as miyadaiku — temple carpenters — and the inherited reflex is that doing the careful manual work yourself is a form of integrity. That reflex serves me well in art and in code review. It does not serve me at 1 a.m. on a Tuesday. The pipeline below is the compromise: keep human judgment in the loop where it matters, and let a system handle the mechanical correlation work.
The full pipeline at a glance
Five stages, each isolated so a failure surfaces in Slack with the stage name attached:
[1] → [2]: webhook arrival to normalized record, under 5 seconds
[2] → [3]: dedup-pass to dSYM resolved, under 30 seconds
[3] → [4]: symbolication done to Claude Code launch, under 10 seconds
[4] → [5]: RCA report generated and PR drafted, total under 90 seconds end-to-end
The 90-second target is calibrated against a specific human moment: the time between the Slack notification sound and the developer being able to assess the situation from a phone screen. Anything longer and we end up opening Xcode anyway, which defeats the point.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦A complete, copyable Cloud Functions + GitHub Actions pipeline that takes a Crashlytics Issue webhook to a fully symbolicated, blast-radius-aware draft PR in 90 seconds — every YAML, shell, and TypeScript snippet you need is included
✦Concrete operating targets from running this at 50M+ downloads across 14 titles: 99.74% Crash-free Users, 8-minute MTTD, 4h12m MTTR, $42/month Claude API cost, and a 12% false-positive rate you can plan against
✦A reviewer workflow that pairs Claude-generated RCA reports with human PR review to process 17 incidents/week without sacrificing code quality — including the four pitfalls that nearly broke the rollout in week one
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
I keep the ingest function deliberately small. Firebase Extensions can push Crashlytics events into a Pub/Sub topic; the Cloud Function reads from there, deduplicates aggressively, and only enqueues genuinely new work for Claude.
// functions/src/crashlytics-ingest.tsimport { onMessagePublished } from "firebase-functions/v2/pubsub";import { Firestore, FieldValue } from "@google-cloud/firestore";const db = new Firestore();const DEDUP_WINDOW_MIN = 30; // do not reprocess the same issue within 30 minutesexport const ingestCrashlyticsIssue = onMessagePublished( { topic: "crashlytics-issues", region: "asia-northeast1", memory: "512MiB" }, async (event) => { const payload = event.data.message.json as CrashlyticsPayload; const issueKey = `${payload.app.bundleId}:${payload.issue.id}`; const ref = db.collection("crash_triage").doc(issueKey); const snap = await ref.get(); if (snap.exists) { const last = snap.data()?.lastSeenAt?.toDate(); const minutesSince = last ? (Date.now() - last.getTime()) / 60000 : Infinity; if (minutesSince < DEDUP_WINDOW_MIN) { await ref.update({ recurrenceCount: FieldValue.increment(1), lastSeenAt: FieldValue.serverTimestamp(), }); return; // skip Claude invocation } } await ref.set({ bundleId: payload.app.bundleId, version: payload.app.version, issueId: payload.issue.id, title: payload.issue.title, stackTraceRaw: payload.issue.exception.stack_trace, affectedUsers: payload.issue.distinct_counts.users, crashCount: payload.issue.distinct_counts.events, firstSeenAt: FieldValue.serverTimestamp(), lastSeenAt: FieldValue.serverTimestamp(), recurrenceCount: 1, status: "queued", }, { merge: true }); await db.collection("triage_queue").add({ issueKey, enqueuedAt: FieldValue.serverTimestamp(), }); });
The 30-minute dedup window matters. Crashlytics emits frequent re-notifications for the same crash hash, and without this gate the Claude API quota will be consumed inside an hour. I started with a 5-minute window and saw a 41% reduction in API calls after widening it to 30 minutes, with no measurable loss of meaningful signal.
Stage [3]: dSYM download and symbolication with atos
With Bitcode deprecated (Xcode 14 and later), the stable path is App Store Connect API plus Fastlane. I run fastlane refresh_dsyms on a daily CI schedule, dropping the dSYMs into gs://wallpaper-crash-pipeline/dsyms/<bundleId>/<version>/<uuid>.zip. At triage time we pull the matching one by image_uuid.
atos_resolve.py is a thin wrapper that shells out to atos -arch arm64 -o <BINARY> -l <LOAD_ADDR> <ADDR> for each frame and writes a Firestore-shaped JSON. Average wall time per issue: 8.2 seconds. When a dSYM cannot be found — typically debug builds from test devices — we set status: "unsymbolicatable" and skip the Claude call entirely. Letting Claude reason over hex addresses produces fluent nonsense.
Stage [4]: Claude Code headless RCA
This is the heart of the pipeline. Claude Code runs in headless mode from GitHub Actions, with a deliberately narrow --allowedTools list. No write tools are passed in — the model can read code, grep, and inspect git history, but cannot edit files. Any modifications happen later, in a human-reviewed PR.
The prompt template enforces a four-section structure on the output: (1) top three root-cause hypotheses with confidence scores, (2) commits from the last 30 days that plausibly relate, (3) a Swift draft fix, (4) a rollback recommendation (recommended / conditional / not needed). I pin Claude Sonnet 4.6 at temperature 0.2 with a 4,096-token output ceiling.
Across the 67 issues processed so far, the first hypothesis was the actual cause 78% of the time, the correct hypothesis appeared in the top two 91% of the time, and the draft Swift fix was adopted with minor edits 34% of the time. The remaining 66% required a human rewrite, but average reviewer time dropped from 14 minutes (writing from scratch) to about 5 minutes (editing a Claude draft).
Stage [5]: Draft PR creation and Slack handoff
The final stage opens a draft PR with the RCA markdown as the body and sends a Slack message that contains just enough context to decide "now" vs. "tomorrow morning."
I deliberately stop short of auto-merging. The Slack message includes the current Crash-free Users number and the 24-hour growth rate of affected users — enough to triage from a phone screen, but the actual merge always passes through a human review.
Three weeks of operating numbers
What follows is real telemetry from the first three weeks of May 2026. Take them as anchor points for sizing your own setup, not as commitments — your crash mix and team will produce different shapes.
Issues processed: 67 (22 of them non-fatal)
Median time, Crashlytics notification to draft PR: 71 seconds (p90: 142 seconds)
MTTD (Mean Time to Detect): 8 minutes (was 2h 30m)
MTTR (Mean Time to Resolve): 4h 12m (was 9h 18m)
Claude API cost: ~$42/month for ~1,400 Sonnet 4.6 calls
False-positive rate (Claude flagged an unrelated commit): 12%
The MTTR halving is, more than anything, a quality-of-life win. When a crash lands at midnight I no longer have to write code at midnight; I review at midnight, and the actual fix happens with daylight judgment.
Four traps worth knowing before you start
dSYM upload failures fail silently. Since Xcode 16, setting Debug Information Format to "DWARF with dSYM File" is not enough on its own — dSYMs only reach App Store Connect via the archives path. Wire Fastlane's upload_symbols_to_crashlytics into your CI and verify daily.
Don't dedup on issue title. Two EXC_BAD_ACCESS crashes can have identical titles and entirely different image+offset signatures. The Crashlytics-side issue.id hash is the correct dedup key.
Don't feed Claude the full stack trace. Over 100-frame stacks dilute the signal. Filter to frames inside your own bundle ID, and compress SDK internals to the top five lines. RCA precision improved noticeably after I added this filter.
Do not auto-merge draft PRs. This is the strong recommendation. Even when RCA confidence is high, Swift fixes have side-effect surface area that human review catches. At 17 issues per week, gating on a human reviewer is well within scaling capacity and prevents the "one fix introduces a different bug" pattern.
Where to start if you adopt this
You do not need to build all five stages at once. If I were starting again from zero, I'd sequence it like this:
Week one: ship stages [2] and [3] only, with atos output going straight to Slack. That alone moves MTTD from hours to roughly 15 minutes. Week two: add stage [4] and post the RCA report to Slack. Stage [5] (the draft PR) can wait until week three, when you trust the report content.
Before any of this, measure your current MTTD and MTTR for a baseline. Compute it from the difference between Crashlytics firstSeenAt and the GitHub issue createdAt for 30 days of history. Without that baseline, the after-effect tends to feel imagined rather than measured.
Running indie apps for long enough teaches you that quality cost grows faster than feature velocity. Each release adds to the surface area of past code that must keep working, and the cumulative weight of triage gets heavier without you noticing. Aiming Claude Code at the most mechanical part of that weight has, for me, been the single change that bought back the most evening time this year.
The pipeline is not finished — that 12% false-positive number is the next thing I want to lower. If you're working on something similar and have ideas, I'd love to hear them. Thanks for reading.
Share
Thank You for Reading
Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.