# Crash Reporting Strategy

> Crash reporting for mobile - symbolication, SLOs, tagging, triage workflow, and comparing Crashlytics, Sentry, Bugsnag, Firebase, and native (APM) reporters. Use when setting up or improving crash reporting.

- Skill: `almasumdev/crash-reporting-strategy` (Agent Skill)
- Install (CLI): `npx skillmds@latest add almasumdev/crash-reporting-strategy`
- Raw SKILL.md: https://api.skillmd.com/api/skills/almasumdev/crash-reporting-strategy/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Author: almasumdev (https://skillmd.com/u/almasumdev)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/almasumdev/crash-reporting-strategy

---


# Crash Reporting Strategy

## Instructions

Crash reports are the most reliable signal from real users. Without a disciplined strategy they become noise. Good crash reporting gives you an actionable ranked list, readable stack traces, and an SLO you can hold yourself to.

### 1. Pick a Reporter (or two)

| Reporter | Strengths | Notes |
| --- | --- | --- |
| Firebase Crashlytics | Free, deep Android + iOS support | Default for most teams |
| Sentry | Unified across mobile, web, backend; release health; rich context | Pair with Sentry for tracing/logging |
| Bugsnag | Release stability scores | Paid |
| Instabug, Embrace, Shake | Session replay, user-context | Heavier footprint |
| Xcode Organizer + Play Console | Always-on, no SDK needed | Limited grouping, no pre-release |

Running two reporters is reasonable (e.g., Crashlytics for crash signal + Sentry for release health + logs). Two is the max.

### 2. Symbolication

Unsymbolicated stack traces are useless. Upload debug symbols on every release.

- **iOS**: upload `dSYM`s after every archive. Crashlytics and Sentry both have Fastlane plugins. Bitcode is deprecated; modern builds no longer require the bitcode compiler re-symbolication dance.
- **Android**: upload **ProGuard/R8 mapping** and **native symbol files (`.so` debug symbols)** per build.
- **Flutter**: upload `--split-debug-info` symbols.
- **React Native**: upload **Hermes bundle + source map** for the JS side, plus native symbols for the native side.

Automate upload in the pipeline; fail the release if upload fails.

### 3. Release Tagging

Every event carries:

- `app.version` and `app.build`.
- `env` (dev, staging, prod).
- `flavor` if applicable.
- `releaseStage` (beta, production).
- `deviceModel`, `osVersion`, `locale`.
- `userId` as a hashed id, never PII.
- Current screen / feature flag state on crash.

```swift
Crashlytics.crashlytics().setUserID(hashedUserId)
Crashlytics.crashlytics().setCustomValue(flags.value("checkout_v2"), forKey: "checkout_v2")
```

```kotlin
FirebaseCrashlytics.getInstance().setUserId(hashedUserId)
FirebaseCrashlytics.getInstance().setCustomKey("checkout_v2", flags.bool("checkout_v2"))
```

### 4. Breadcrumbs and Logs

- Record navigation events, network requests (method + path only, no query strings with secrets), and important state transitions as breadcrumbs.
- Attach the last 20-50 breadcrumbs to each crash.
- Do not log PII, tokens, or message content.
- Use `CLS_LOG` / `SentryBreadcrumb` or equivalent; avoid sending every `print` to the reporter.

### 5. Handled Exceptions

Not every error is a crash. Handle errors in domain code, report the serious ones:

- `FirebaseCrashlytics.recordException(...)` or `Sentry.captureException(...)` for errors that indicate a bug, not user input.
- Tag them with severity so they do not dominate the crash list.
- Never report user-cancelled or offline-expected errors.

### 6. SLOs

Define crash SLOs and display them on a dashboard.

- **Crash-free sessions**: 99.8% (typical target for a mature consumer app; higher for fintech/health).
- **Crash-free users**: 99.5%.
- **ANR rate** (Android): below 0.47% (Play Console threshold).
- **Foreground hang rate** (iOS): monitor via Xcode Organizer "Hangs" metric.

Gate rollouts on these (see `staged-rollouts`).

### 7. Triage Workflow

- **Daily triage** during active release. New or regressed issues get an owner within 24 hours.
- Group issues by stack trace fingerprint; the reporter does this, but review for false groups.
- **Rank by user impact**, not raw count. A crash affecting 0.5% of users in onboarding beats 50 crashes in a settings corner.
- **Assign owners** per area (payments, sync, onboarding) so issues route fast.
- **Version hygiene**: do not fight crashes from two versions ago; focus on current + previous.

### 8. Actionable Rules

- Every new-in-release issue must be acknowledged before the next rollout step.
- Every top-10 issue has an assignee, a hypothesis, and a reproduction attempt.
- Every fix must reference the issue ID in the commit message.
- Crashes that cannot be reproduced get a defensive fix (null check, guard) only if the context justifies it, never a silent swallow.

### 9. Beware of Blind Spots

- **Early-boot crashes**: many reporters require the SDK to be initialized before the crash. Pair with a native `uncaughtException` logger or platform breadcrumbs.
- **Background processes**: some reporters do not capture crashes in separate processes (widgets, app extensions, `isolate`s). Configure per process.
- **ProcessLifecycle on Android** differs from Activity lifecycle. Use the broader one for app-level breadcrumbs.
- **React Native**: JS errors and native errors are different worlds. Capture both.
- **Flutter**: Dart errors via `FlutterError.onError` and `PlatformDispatcher.instance.onError` (async errors) separately.

```dart
void main() {
  runZonedGuarded(() {
    FlutterError.onError = (details) => Crashlytics.recordError(details.exception, details.stack);
    PlatformDispatcher.instance.onError = (e, s) { Crashlytics.recordError(e, s); return true; };
    runApp(const MyApp());
  }, (e, s) => Crashlytics.recordError(e, s));
}
```

### 10. Anti-Patterns

- Shipping without symbol upload. You will regret it.
- Logging PII or secrets in breadcrumbs.
- Silencing crashes with broad `catch` blocks to clean up dashboards.
- Chasing old-version tails. Fix current and previous; accept drift beyond that.
- Ignoring ANRs because "they are not crashes". Users see them the same way.
- One reporter with no native-level coverage on a platform.

## Checklist

- [ ] A primary crash reporter is integrated; optional secondary is justified.
- [ ] Symbol uploads happen on every release for every platform.
- [ ] Events tagged with version, build, env, flag state, and hashed user id.
- [ ] Breadcrumbs record navigation, network, and state transitions; no PII.
- [ ] Crash-free session / user SLOs are defined and displayed.
- [ ] Triage workflow has named owners and daily cadence during releases.
- [ ] Blind spots covered: early-boot, background processes, JS/native split.
- [ ] Fixes reference issue IDs in commits for traceability.
- [ ] Handled exceptions are reported selectively with severity tags.

