Skip to content
Back to skills

Crash Reporting And Observability

ASecurity

Wires Sentry crash reporting behind a port: start on consent, PII scrubbing, releases, source maps per build and update. Use when adding Sentry or a DSN, or stacks are minified. Not for analytics (analytics-and-consent).

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 18, 2026
developmentjavascripttypescriptgojavareactnodeawstestinggitperformance

Works with

  • cli

Security analysis

A100/100

Pro scans all 19 files and shows the line behind each finding

Scanned October 18, 2026

npx -y skills add zakariaf/ReactNative-Skills --skill crash-reporting-and-observability --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Crash Reporting And Observability?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Crash Reporting And Observability
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/zakariaf-crash-reporting-and-observability/badge)](https://www.skillsdirectory.com/skills/zakariaf-crash-reporting-and-observability)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: crash-reporting-and-observability
description: "Wires Sentry crash reporting behind a port: start on consent, PII scrubbing, releases, source maps per build and update. Use when adding Sentry or a DSN, or stacks are minified. Not for analytics (analytics-and-consent)."
---

# Crash reporting and observability

Makes an Expo app report its crashes to Sentry through one port and one adapter, plugged into the
canonical root skeleton as a root kit (one registry entry, no root file edited): nothing leaves the
device before the person's choice, nothing personal leaves at all, every error reaches exactly one
issue, and every store build and OTA update can be read in TypeScript source. One zero-dependency
checker proves it on the real code and configs.

## Rules that must hold

1. **`@sentry/react-native` is imported only by `src/services/crash-reporter/sentry-crash-reporter-adapter.ts`
   (never the root entry file, where Sentry's manual setup puts `Sentry.init`); the app reports through `CrashReporterPort`** (`setConsent`, `captureException`, `addBreadcrumb`,
   `flush`, `status`), created once by the root-kit registry (`src/services/create-root-kits.ts`, part of
   the composition root), tests through `createFakeCrashReporter()`, and Jest gets the root mock.
   `sentry-expo` is never installed. *Why:* one file decides what leaves the device, and the real
   SDK in Jest leaks a timer that keeps workers alive.
2. **Nothing is sent before the person's choice, and never from development or tests.** The adapter
   does no I/O when created (no `Sentry.init` in the factory body); `Sentry.init` runs inside `setConsent(true)` only, which
   `useCrashReportingConsent` calls from the crash reporting kit's effects, mounted beside the root
   Stack after the splash release (never the registry, the composition root or the root layout); the
   choice comes from a consent source over the settings field (or `fixedCrashConsent(false)` until the
   app has the row); earlier reports wait in a bounded queue and a refusal drops them; the
   development variant and an empty DSN never start the SDK. The settings default follows the
   contract's `consentModel` (`opt-in` by default). *Why:* crash data is personal data under most
   privacy laws, and startup must open no connection before the first frame.
3. **The options are the privacy-first set.** `sendDefaultPii: false` written; no screenshots, view
   hierarchy, Session Replay, feedback widget, logs or failed-request capture. *Why:* each of those
   sends what the person typed, saw or requested; Sentry's own Expo snippet turns several on.
4. **Every event and breadcrumb passes the scrubber, and no report names a person.** `beforeSend`,
   `beforeSendTransaction` and `beforeBreadcrumb` call `crash-scrubber.ts`; there is no `setUser`
   (only `setUser(null)`), and the port has no member for it; the developer turns on Sentry's
   server-side scrubbing. *Why:* `beforeSend` never sees native crashes, but the JavaScript scope is
   synced into them, so identity set anywhere ends up in reports the scrubber cannot touch.
5. **Each error reaches Sentry exactly once.** The error net keeps Hermes' rejection tracker
   (`reactNativeErrorHandlersIntegration({ onunhandledrejection: false })`), Sentry keeps the global
   handler (it flushes fatal errors, and both native SDKs drop React Native's fatal-JS crash expecting
   it), and the root skeleton's error sink records everything locally and hands each report to the
   kit's `forward: createCrashForwarder(crashReporter)`, which sends exactly `render` and
   `unhandled-rejection` (the reports of the first moments included: the sink replays them). *Why:*
   Hermes keeps one tracker (last caller wins), and a forwarded `global-handler` report would
   duplicate every uncaught exception.
6. **Breadcrumbs carry route patterns and fixed event names, never content.** Route crumbs come from
   `useSegments()` (`tasks/[id]`), never `usePathname()` or search params; console, touch and UI crumbs
   are dropped; no `Sentry.wrap` or `TouchEventBoundary`. *Why:* paths, console lines and pressed
   labels hold ids, search text and titles.
7. **Release and dist come from the native build; environment is the variant.** No `release` or
   `dist` in the options (`<id>@<version>+<build>` and `<build>` come from native);
   `environment: config.environment`. *Why:* a JavaScript release splits one build into two releases
   and no longer matches the uploaded symbols; without `environment` every preview crash is filed under
   production.
8. **Every bundle reports its debug ID and every reporting build uploads its maps and symbols.** Metro
   uses `getSentryExpoConfig`, as the crash reporting Metro kit in `scripts/metro/metro-kits.ts` (the
   canonical `metro.config.js` composes it; never edited); `app.config.ts` lists `@sentry/react-native/expo` with `organization`
   and `project`; profiles in the contract's `uploadProfiles` never set `SENTRY_DISABLE_AUTO_UPLOAD`,
   `SENTRY_ALLOW_FAILURE` or a debug-upload switch; installs keep optional dependencies (the
   `sentry-cli` binary). *Why:* Expo's default config writes a debug ID into the map only, and a
   build without uploads ships crashes nobody can read.
9. **Every OTA update uploads its source maps.** Each script or workflow that runs `eas update` also
   runs `npx sentry-expo-upload-sourcemaps dist`; each EAS Workflows `type: update` job sets
   `upload_sentry_sourcemaps: true` (or uploads in its `after_update` hook); ota-updates' restart
   path awaits `crashReporter.flush()` before reloading (that skill owns the step). *Why:* an update's bundle was never built natively, so nothing else uploads it.
10. **The upload token is a build secret.** `SENTRY_AUTH_TOKEN` lives in EAS (secret visibility) or CI
    secrets; never in the plugin options, the app config, a committed `.sentryclirc` or
    `sentry.properties`; `.env.sentry-build-plugin` is git-ignored. *Why:* the plugin writes an
    `authToken` option into the native projects, and the token grants write access to the org.
11. **Tracing is off by default, capped and declared.** `TRACES_SAMPLE_RATE` and
    `PROFILES_SAMPLE_RATE` are literals in `src/config/crash-reporting-config.ts`, at most the
    contract's `maxTracesSampleRate`, no `tracesSampler`; a rate above 0 declares `performance-data`.
    *Why:* tracing multiplies volume and is a separate data type in both stores' declarations.
12. **`contracts/crash-reporting.json` states the facts and the store declarations match them**:
    provider, consent model, tracing cap, upload profiles, and `collects` (`crash-data`,
    `other-diagnostic-data`, `performance-data`), never linked to the user or used for tracking; the
    `@sentry/react-native` rows of `contracts/privacy.json` copy them (`privacy-sync`). *Why:*
    privacy-and-store-declarations turns these facts into App Privacy, Data safety and privacy-manifest answers.
13. **The developer owns the Sentry account; prove it on a release build.** Ask for the org, project
    and DSN (never invent them), hand over the `eas env:set` commands and server-side settings, and
    stop and ask before anything that sends data to their project or changes their account. *Why:*
    those are outward-facing actions on someone else's service.

## Workflow

1. **Decide with the developer.** Read [references/privacy-consent-and-scrubbing.md](references/privacy-consent-and-scrubbing.md)
   (sections 6 to 9) and agree the consent model, whether tracing is on, the upload profiles and the
   org/project slugs. Copy `templates/contracts/crash-reporting.json` (+ schema) and fill the facts.
2. **Install:** `npx expo install @sentry/react-native` (never `@latest`); keep optional dependencies;
   rebuild the development client. Details: [references/setup-and-wiring.md](references/setup-and-wiring.md).
3. **Copy the templates** at the paths their first lines name: `templates/src/services/crash-reporter/`
   (port, adapter, scrubber, forwarder, fake and the root kit), `templates/src/config/` (the config,
   its reader and the DSN's type in `crash-reporting-env.d.ts`), `templates/src/hooks/use-crash-reporting-consent.ts`,
   `templates/src/hooks/use-route-breadcrumbs.ts` (each with its test), `templates/scripts/metro/metro-kits.ts`
   and `templates/__mocks__/@sentry/react-native.ts`. The root skeleton's slots come from
   app-startup-and-bootstrap; set them up with it first.
4. **Register the kit** in app-startup-and-bootstrap's registry `src/services/create-root-kits.ts`
   ([examples/create-root-kits.ts](examples/create-root-kits.ts)): create the adapter from
   `readCrashReportingConfig()` and add `createCrashReportingKit({ crashReporter, consent })` to the
   list. The root files stay unchanged: the kit's `forward` reaches the error sink and its effects
   (the choice applied after the splash, route breadcrumbs) mount beside the root Stack.
5. **Give the person the choice** ([examples/settings-schema.ts](examples/settings-schema.ts)): add
   `crashReportsEnabled` the settings-and-preferences way (default from the consent model) with its
   settings row, whose hint says that turning it back on after turning it off applies at the next
   launch, and pass the kit a consent source over that field (the example's `settingsCrashConsent`).
   Until the row exists, pass `fixedCrashConsent(false)`. Read
   [references/error-flow-breadcrumbs-and-tracing.md](references/error-flow-breadcrumbs-and-tracing.md).
6. **Build config** (`templates/scripts/metro/metro-kits.ts`, [examples/app.config.ts](examples/app.config.ts)):
   the Metro kit builds the base config with `getSentryExpoConfig` (the canonical `metro.config.js`
   composes it and keeps `cacheVersion`); the plugin entry with `organization` and `project`;
   `SENTRY_DISABLE_AUTO_UPLOAD=true` only in non-reporting eas.json profiles; append
   `&& npx sentry-expo-upload-sourcemaps dist` to every `eas update` script. Read
   [references/releases-symbols-and-sourcemaps.md](references/releases-symbols-and-sourcemaps.md).
7. **Hand over the human steps** and stop until the developer confirms: the `eas env:set` commands
   (DSN plain text per reporting environment, `SENTRY_AUTH_TOKEN` secret), Sentry's server-side
   scrubbing settings, and the declarations for privacy-and-store-declarations (App Privacy, Data
   safety, `ios.privacyManifests` entries).
8. **Run the tests:** `npx jest src/services/crash-reporter src/hooks src/config scripts/metro --ci --maxWorkers=2`,
   then the app's typecheck and lint.
9. **Run the checker:** `node ${CLAUDE_SKILL_DIR}/scripts/check-crash-reporting.mjs .` (multi-package:
   `--app-dir apps/<app>`). Fix every `FAIL` line ([references/testing-and-checker-rules.md](references/testing-and-checker-rules.md)
   explains each rule) and rerun until `RESULT: PASS`.
10. **Before the first store release:** the device proof on a preview build and a named simulator and
    emulator (testing reference, section 5), with the developer's go-ahead.
11. **Report** the consent model, the files changed, the SKIP lines, the open human steps, and every
    `RESULT` line.

## Definition of done

- [ ] Only the adapter imports `@sentry/react-native`; the registry creates it and registers `createCrashReportingKit`; tests use the fake; the root mock is in `__mocks__/@sentry/`.
- [ ] The SDK starts only from `setConsent(true)`, applied by the kit's effects after the splash; the settings default matches `consentModel`; the development variant never reports.
- [ ] The options are the privacy-first set with the scrubber hooks, `environment`, the rejection-tracker integration, and no `release`, `dist`, `setUser` or `Sentry.wrap`.
- [ ] The kit forwards through `createCrashForwarder`: exactly `render` and `unhandled-rejection`.
- [ ] The Metro kit uses `getSentryExpoConfig`; the plugin entry has `organization` and `project` and no token; upload profiles keep uploads on; every `eas update` uploads its maps.
- [ ] `contracts/crash-reporting.json` is filled; tracing is 0 or within the cap and declared.
- [ ] The developer confirmed the EAS variables, the server-side scrubbing settings and the store declarations, or the report lists them as open.
- [ ] The tests in workflow step 8 pass, with the app's typecheck and lint.
- [ ] Expected SKIP only: `SKIP eas.json [upload-not-disabled] not yet created (eas-build-and-submit creates it)` before eas.json exists.
- [ ] `node ${CLAUDE_SKILL_DIR}/scripts/check-crash-reporting.mjs .` prints `RESULT: PASS`

## Anti-patterns

- **Pasting Sentry's setup snippet.** It sets `sendDefaultPii: true`, replay sample rates and
  `Sentry.wrap(RootLayout)`, and calls `Sentry.init` at module scope: data leaves before any choice.
  Copy the adapter template.
- **`Sentry.init` in `index.ts`, `App.tsx`, `_layout.tsx` or the composition root.** It starts the SDK and its connection
  during startup and before consent; the kit's effects apply the choice after the splash.
- **Wiring crash reporting into `app-root.tsx`, `app-stack.tsx` or `metro.config.js`.** The root
  skeleton stays canonical: register the kit in `src/services/create-root-kits.ts` and list the Metro
  kit in `scripts/metro/metro-kits.ts` (check-startup's `root-kits` fails a kit imported by a root file).
- **Turning off Sentry's global handler "to avoid duplicates".** Native SDKs drop React Native's
  fatal-JS crash expecting the JavaScript handler, so fatal errors vanish; turn off only the rejection
  tracker and forward rejections from the sink.
- **Forwarding every net source to `captureException`.** Uncaught exceptions arrive twice.
- **`setUser({ id: account.id })` "for support".** It is synced into native crash reports and changes
  the store declarations; give support the event id instead.
- **Breadcrumbs from `usePathname()`, console logs or `Sentry.wrap` touch tracking.** Ids, search text
  and titles leave the device.
- **Setting `release` to `version` or a git SHA in JavaScript.** Native events and uploads use the
  native name; the JavaScript one splits the build.
- **`SENTRY_DISABLE_AUTO_UPLOAD=true` in a shared base profile** to make development builds pass: the
  production profile inherits it. Put it in the development profile only.
- **`authToken: process.env.SENTRY_AUTH_TOKEN` in the plugin entry.** Prebuild writes the token into
  the native projects; sentry-cli reads the environment by itself.
- **Reporting expected failures** (offline, validation, a handled 404): they bury real crashes and
  spend the quota; they are `Result` values.

## Files in this skill

| File | What it is | Read/run when |
|---|---|---|
| [references/setup-and-wiring.md](references/setup-and-wiring.md) | Install, the files, DSN and variant, composition root, root mock, config plugin, Metro, EAS variables, Expo Go, multi-package | Workflow steps 2 to 4 and 6 |
| [references/privacy-consent-and-scrubbing.md](references/privacy-consent-and-scrubbing.md) | What a report holds, the option set, the scrubber and its limits, no identity, server-side scrubbing, consent models and timing, store declarations, privacy manifest, children's apps | Workflow steps 1, 5 and 7 |
| [references/error-flow-breadcrumbs-and-tracing.md](references/error-flow-breadcrumbs-and-tracing.md) | Who catches what, the rejection tracker and global handler, the sink, defects vs expected failures, breadcrumb rules, tracing, flush before OTA reload | Workflow step 5 |
| [references/releases-symbols-and-sourcemaps.md](references/releases-symbols-and-sourcemaps.md) | Release, dist, environment; debug IDs; EAS and local uploads; OTA uploads; Hermes; proving symbolication; failure modes | Workflow step 6, minified stacks |
| [references/testing-and-checker-rules.md](references/testing-and-checker-rules.md) | What the tests prove, testing reporting code, every checker rule with its fix, SKIP and NOT APPLICABLE, the device proof, pitfalls | Workflow steps 8 to 10 |
| `templates/src/services/crash-reporter/` | Port, Sentry adapter, scrubber, forwarder, fake, and the root kit `crash-reporting-root-kit.tsx` (+ tests) | Workflow steps 3-4 |
| `templates/src/config/` | `crash-reporting-config.ts`: the DSN and variant parser, `readCrashReportingConfig()` and the sample-rate constants (+ test); `crash-reporting-env.d.ts`: the DSN variable's type | Workflow step 3 |
| `templates/scripts/metro/metro-kits.ts` | The Metro kit list with Sentry's `getSentryExpoConfig` (the canonical `metro.config.js` composes it) | Workflow step 6 |
| `templates/src/hooks/` | `use-crash-reporting-consent.ts` (applies the saved choice after the splash) and `use-route-breadcrumbs.ts` (route-pattern breadcrumbs), each with its test | Workflow step 3 |
| `templates/__mocks__/@sentry/react-native.ts` | Root Jest mock of the SDK | Workflow step 3 |
| `templates/contracts/` | `crash-reporting.json` and its schema | Workflow step 1 |
| [examples/create-root-kits.ts](examples/create-root-kits.ts) | The root-kit registry with the crash reporting kit and a consent source over the settings store | Workflow steps 4-5 |
| [examples/settings-schema.ts](examples/settings-schema.ts) | The `crashReportsEnabled` settings field | Workflow step 5 |
| [examples/app.config.ts](examples/app.config.ts) | The config plugin entry | Workflow step 6 |
| `scripts/check-crash-reporting.mjs` | The checker (29 rules; `--help` lists them) | Workflow step 9, definition of done |
| `scripts/lib/` | `crash-scan.mjs` (the checker's source helpers); `source-scan.mjs`, `object-literal.mjs` (shared, synced; do not edit) | With the checker |
| `scripts/check-lib.mjs` | Shared checker library (synced; do not edit) | With the checker |
| `scripts/selftest.mjs` | Proves the checker against the fixtures | After changing the checker |
| `tests/bases/app/` | The fixture base app (package.json, eas.json, .npmrc, .gitignore) | With the self-test |
| `tests/fixtures/` | One good case, a bad case per rule, pass and error cases | With the self-test |
| `assets/shared.json` | Which shared files this skill syncs | When syncing |

## Related skills

- `error-handling-typed-results` - the error net, the crash fallback and `Result` values.
- `architecture-and-boundaries` - ports, adapters, the composition root, the error log.
- `app-startup-and-bootstrap` - the root skeleton's slots and registry the kit plugs into, and the startup order.
- `settings-and-preferences` - adding the `crashReportsEnabled` field and its row.
- `analytics-and-consent` - product analytics and its own consent.
- `privacy-and-store-declarations` - App Privacy, Data safety, the privacy manifest, the network inventory.
- `app-variants-and-environment` - `EXPO_PUBLIC_*` readers and EAS secrets.
- `eas-build-and-submit` - profiles, remote versions and the build environment.
- `ota-updates` - publishing updates and the flush before reload.
- `performance-budgets` - startup and frame budgets measured in test builds.
- `debug-menu-and-test-hooks` - test-only crash triggers for the device proof.
- `unit-and-component-tests` - root mocks and the fake-based test style.

Files in this skill

  • SKILL.md18.7 KB
  • assets/shared.json332 B
  • examples/app.config.ts1.7 KB
  • examples/create-root-kits.ts2.4 KB
  • examples/settings-schema.ts2.1 KB
  • references/error-flow-breadcrumbs-and-tracing.md10.6 KB
  • references/privacy-consent-and-scrubbing.md16.8 KB
  • references/releases-symbols-and-sourcemaps.md10.5 KB
  • references/setup-and-wiring.md14.3 KB
  • references/testing-and-checker-rules.md14.9 KB
  • scripts/check-crash-reporting.mjs52.4 KB
  • scripts/lib/crash-scan.mjs10.4 KB
  • scripts/lib/object-literal.mjs3.5 KB
  • scripts/lib/source-scan.mjs35.1 KB
  • scripts/selftest.mjs654 B
  • templates/__mocks__/@sentry/react-native.ts992 B
  • templates/contracts/crash-reporting.json487 B
  • templates/contracts/crash-reporting.schema.json1.9 KB
  • templates/scripts/metro/metro-kits.ts983 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.