| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
…it app-exit telemetry Post-mortem visibility on OS-driven kills, answering "does our backend stay alive long enough on this user's device?" per platform. Android (Phase 6): ExitReasonsCollector surfaces getHistoricalProcessExitReasons records (API 30+) as "android exit: REASON_*" events on the next process start — main process via a new ApplicationLifecycleListener, FGS from ComapeoCoreService.onCreate, each reporting its own process only. A per-process high-water timestamp dedupes across runs (first observation initialises it and emits nothing, so device updates never flood Sentry with the pre-feature backlog). oem.killer.suspected tags the SIGKILL-to-foreground(-service) signature of OEM custom killers. Wall-clock anchors (BackgroundAnchors, ProcessLifecycleOwner ON_STOP/ON_START) derive uptime/backgrounded-for buckets. Pre-30 devices set a one-time exitReasons.supported=false scope tag. iOS (Phase 7a): AppExitMetricsCollector subscribes to MXMetricPayload — the MetricKit metric side sentry-cocoa deliberately skips — and forwards MXAppExitMetric buckets as "ios exit: <cohort>_<bucket>" events with cause-class/intentional tags and a stable window_id. The decode logic (AppExitDecoder) is MetricKit-free so it compiles and tests on macOS via swift test. Phase 9b.8's tier reclassification ships here rather than later: the duration-derived Android fields and the iOS per-exit duplication only flow when captureApplicationData is on; the records themselves are diagnostic-tier. Divergence from plan: the pre-iOS-14 supported=false branch was dropped (podspec floor is 15.1 — dead code). Phase 7b (UserDefaults killed-in-background heuristic) stays in the plan as optional follow-up. Tests: 25 new JVM tests (high-water, tag/level mapping, tier gating, bucket boundaries, decode tables), 11 new Swift tests (duplication semantics, tier gating, unknown-bucket fallthrough). MetricKit usage typechecked against the iOS SDK. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…bering (#84) Post-merge review fixes for the Phase 6/7a exit-reason telemetry (#72). A code review after the merge found a cluster of correctness bugs in the collection pipeline — three of which undermined the headline metrics the feature exists to produce. ## Android **Records were silently consumed without being reported.** Three paths shared one root cause: `collect()` advanced the `last_seen` high-water mark *before* capture, and capture silently no-ops on an uninitialised Sentry hub. - The main process collected at `Application.onCreate`, but main-process sentry-android only comes up when JS-side `Sentry.init` runs (manifest sets `io.sentry.auto-init=false`) — so main-process exit records were dropped on essentially every cold start. - The FGS ran the collector even with `diagnosticsEnabled=false`, consuming records a user opting in later could never see. Fix: collection no-ops (leaving records pending) until `Sentry.isEnabled()`, the main process waits up to 2 minutes for the JS-triggered init, and the high-water mark advances only after the capture loop (at-least-once; duplicates absorbed by the stable message grouping). Both callers snapshot the previous session's anchors (`AnchorSnapshot`) before stamping their own, so collection can run arbitrarily late without reading current-run values. **`comapeo.fgs.killed_in_background` was systematically `false`** in the dominant kill→relaunch flow: the relaunch's replayed `ON_START` zeroed `backgrounded_at` before the FGS (started only after foregrounding) read it. Fix: `ON_START` now stamps a paired `foregrounded_at` anchor instead of clearing; the decoder treats an exit as in-background when the last stamp before its timestamp was a background. Neither stamp is ever cleared, so the answer is correct however late collection runs. **Cross-process SharedPreferences clobbering.** Both processes wrote the same prefs file, and SharedPreferences rewrites the whole file from a per-process cache — one process's write could revert or erase the other's anchors, high-water marks, or even the Sentry toggles. Fix: one anchors file per process (`com.comapeo.core.anchors.<proc>`) with exactly one writer each; the FGS reads the `main` file read-only. Writes use `commit=true` so the `backgrounded_at` stamp survives a SIGKILL right after backgrounding (the exact window this feature measures). Note: the file move resets the high-water mark once — the first run on this code re-initialises it and emits nothing, then reporting resumes. **Shared record window.** `getHistoricalProcessExitReasons(pkg, 0, 10)` returns the newest 10 records across *all* processes of the package, so FGS churn (START_STICKY restart loops) could evict main-process death records before they were ever seen. Fix: query without a cap (the OS bounds retention at ~16 per package) and keep the newest 10 per process after filtering. Smaller cleanups: `ExitReasonTags.levelFor` returns `SentryLevel` directly (was a string re-parsed by a second when-block that silently degraded unknown values to INFO), and the FGS process name is derived at runtime via a shared helper instead of a literal copy of the manifest's `android:process` — a rename can no longer silently break the record filter. ## iOS - **Double-counted foreground OOM/watchdog deaths**: sentry-cocoa's watchdog-termination tracking (enabled by default) already reports these as errors, so the foreground `memory_resource_limit` / `app_watchdog` MetricKit buckets are demoted to warning — same policy the crash buckets already followed. Background buckets stay error (the SDK heuristic only covers foreground deaths). - **Unbounded event bursts**: app-usage-tier per-exit duplication is now capped at 10 events per window+bucket — counts are 24h cumulative totals and benign buckets (background `normal_app_exit`) reach the hundreds, burning quota in a burst on every daily payload. `window_count` keeps the exact count; query `sum(window_count)` when exactness matters. Docs (`docs/sentry-integration.md` §7.5.1/§7.5.2) updated to match. ## Tests - Android JVM: 27 pass, including new coverage for the high-water return-don't-persist contract, the per-process cap, and the fg/bg-stamp-vs-exit-timestamp ordering (incl. the relaunch regression case). - iOS package: 103 pass, including new coverage for the per-cohort level split and the duplication cap. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…pplication Metrics (#73) Stacked on #72 (merge that first; this PR's base is `sentry-exit-telemetry`). Sentry's Application Metrics (count/gauge/distribution with attributes; no trace context required; no issue lifecycle) is the right primitive for stats-gathering telemetry like the exit reasons landed in #72 — events there create perpetually-"unresolved" issues and regression-alert noise. `@sentry/react-native` v8 lock-steps native SDKs that have the metrics API (sentry-android 8.43, sentry-cocoa 9.15), so this PR does the migration and the flip together. ## SDK migration - **peer + dev dep** `@sentry/react-native` `^7.13.0` → `^8.13.0` — ⚠️ **breaking for consumers**: the host app must upgrade its Sentry SDK in the same release (v8 floors: iOS 15+, Xcode 16.4+, AGP 7.4+, Kotlin 1.8+ — all already satisfied by this module/Expo 55). - **iOS**: `Sentry/HybridSDK '8.58.0'` → `Sentry '9.15.0'` in the podspec (cocoa 9 dropped the `HybridSDK` subspec; RNSentry v8 depends on the plain pod), matching `exact:` pin in `Package.swift`. `check-sentry-cocoa-pin.mjs` updated for RN v8's podspec shape (plain `Sentry` dep, version via the `sentry_cocoa_version` Ruby variable). - **Android**: `sentry-android-core` 8.32.0 → 8.43.0. - **cocoa 9 API drift**: exactly one call site (`TransactionContext` gained nullable `sampleRate`/`sampleRand`). The `@_spi(Private)` surfaces the bridge relies on (`SentryEventDecoder`, `PrivateSentrySDKOnly`) survive the major unchanged. ## Exit telemetry → `comapeo.app.exit` count metric - **Android**: one count per record via `Sentry.metrics().count`, former tags+extras as attributes; event level becomes the `exit.severity` attribute (`error`/`warning`/`info`). Same high-water/anchor pipeline and tests. - **iOS**: one count per window+bucket with the cumulative value as the metric value — the per-exit event duplication and its `window_id` dedup machinery are deleted; a count carries N natively. - Both SDKs ship metrics enabled by default (`enableMetrics`/`Metrics.enable` default true), so no init changes. ## Privacy re-tiering — metrics need fewer opt-ins Metrics are aggregate and low-cardinality (pre-bucketed tags, no per-user timeline, no free text), so coarse fields moved to the always-on diagnostic tier: - Android: `uptime_bucket`, `bg_duration_bucket`, `comapeo.fgs.killed_in_background` now flow at diagnostic; exact `alive_for_ms` / `backgrounded_for_ms` remain behind `captureApplicationData`. - iOS: nothing left gated — the count-per-window shape has no usage-tier residue. The plan is reshaped on the same principle: - Phase 5's per-RPC spans, backend memory checkpoint, and storage-size sample re-tier into Phase 11's **diagnostic metrics inventory** — RPC *timing* becomes `comapeo.rpc.*.duration_ms` distribution metrics at diagnostic; per-RPC *traces* (the privacy-expensive shape) move behind the `debug` toggle. - Phase 11 marked **unblocked**: every layer now has the metrics API, including the backend (`@sentry/node-core` 10.53 ≥ the 10.25 floor), and `comapeo.app.exit` proves the native emission path. ## Validation - `swift test`: 99 tests, 0 failures against cocoa 9.15; MetricKit half typechecked against the iOS 15.1 SDK. - Gradle JVM unit tests green against sentry-android 8.43. - `npm run build` + `lint` green; example app typechecks against v8 (`appStartIntegration` survives). - `check-sentry-cocoa-pin.mjs` passes with the v8 layout. - **Runtime** (API 34 emulator, example app rebuilt on the new SDKs): backend boots to STARTED; SIGKILLed the FGS while foreground-service and the relaunch reported the record through `Sentry.metrics().count` with no errors logged. Consumer note for the example/dev workflow: after pulling this branch, re-run `pod install` in `apps/example/ios` (new pod major + new source file list). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* origin/main: chore(e2e): add e2e tests on browserstack via Maestro (#56) fix(sentry): make exit telemetry lossless and stop cross-process clobbering (#84) feat(sentry): land Phases 6 + 7a — Android exit reasons & iOS MetricKit app-exit telemetry (#72) chore(build): use npm list instead of custom traversal to get native module versions (#70)
## Optic Release Automation This **draft** PR is opened by Github action [optic-release-automation-action](https://github.com/nearform-actions/optic-release-automation-action). A new **draft** GitHub release [v1.0.0-pre.2](https://github.com/digidem/comapeo-core-react-native/releases/tag/untagged-c499977757c9745e56b2) has been created. Release author: @gmaclennan #### If you want to go ahead with the release, please merge this PR. When you merge: - The GitHub release will be published - The npm package with tag pre will be published according to the publishing rules you have configured - No major or minor tags will be updated as configured #### If you close the PR - The new draft release will be deleted and nothing will change ## What's Changed * Android Testing Infrastructure & Bug Fixes by @gmaclennan in #3 * chore: prebuild example/android; harden instrumented tests by @gmaclennan in #10 * Integrate @comapeo/core via IPC over Unix sockets by @gmaclennan in #5 * chore: adjust repo setup by @achou11 in #12 * chore: minor fixes based on expo-doctor by @achou11 in #13 * Add iOS support & test infrastructure by @gmaclennan in #6 * chore: add architecture docs & plans by @gmaclennan in #11 * update some native deps used in backend by @achou11 in #14 * iOS Phase 1: unified JS bundle + smoke test (simulator-only) by @gmaclennan in #15 * iOS Phase 2: xcframework Embed & Sign for native addons by @gmaclennan in #16 * Phase 2 Android: jniLibs packaging + unified rollup loader plugin by @gmaclennan in #17 * chore: post-Phase-2 cleanup — comments, plan docs, agents.md by @gmaclennan in #33 * android: read abiFilters from reactNativeArchitectures (#30) by @gmaclennan in #35 * refactor: simplify build-backend.ts; rollup writes directly to native asset trees by @gmaclennan in #34 * chore: fix eslint configuration by @achou11 in #41 * android: audit 16 KB page alignment on every shipped .so by @gmaclennan in #43 * Add rootkey persistence and lifecycle state management by @gmaclennan in #36 * chore: move example app into apps directory by @achou11 in #18 * refactor: per-component lifecycle state with derived ComapeoState by @gmaclennan in #47 * android: fold waitForFile into connect retry loop by @gmaclennan in #52 * chore: add e2e testing app by @achou11 in #49 * fix(android): drop setUnlockedDeviceRequired from rootkey wrapper key by @gmaclennan in #57 * fix(backend): cache stopping/error frames for late joiners by @gmaclennan in #58 * fix(ios-tests): wait for STOPPING before signalling node exit by @gmaclennan in #59 * fix(android): drain JNI stdio pumps before returning from node::Start by @gmaclennan in #60 * Sentry integration: Phase 1 + Phase 2a + Phase 2b by @gmaclennan in #54 * feat(backend): polywasm-backed undici on iOS, re-enable maps plugin by @gmaclennan in #62 * ci: drop unreliable Android emulator snapshot caching by @gmaclennan in #64 * feat(sentry): land Phase 3 — backend loader + RPC tracing by @gmaclennan in #63 * fix(ios-tests): serialise STOPPING/STOPPED observers in testFullLifecycleStateTransitions by @gmaclennan in #71 * use npm list instead of custom traversal to get native module versions by @achou11 in #70 * feat(sentry): land Phases 6 + 7a — Android exit reasons & iOS MetricKit app-exit telemetry by @gmaclennan in #72 * fix(sentry): make exit telemetry lossless and stop cross-process clobbering by @gmaclennan in #84 * chore(e2e): add e2e tests on browserstack via Maestro by @achou11 in #56 * feat(sentry): migrate to @sentry/react-native v8; exit telemetry as Application Metrics by @gmaclennan in #73 * Map server integration by @gmaclennan in #86 * chore(deps): upgrade to Expo SDK 56 (React Native 0.85) by @gmaclennan in #87 * chore(ci): add release workflow by @gmaclennan in #90 * chore: fix npm script and release build script by @gmaclennan in #91 * chore(pack): don't try to package build files by @gmaclennan in #92 * fix: start fastify listening by @gmaclennan in #93 * perf(backend): switch bundler from rollup to rolldown by @gmaclennan in #94 * fix(ci): ignore-scripts in ios npm installs by @gmaclennan in #96 * fix(ci): replace --ignore-scripts with npm strict-allow-scripts allowlist by @gmaclennan in #106 * feat(config): let the consuming app supply the default project config by @gmaclennan in #95 * chore(release): merge prerelease branch. by @gmaclennan in #110 ## New Contributors * @achou11 made their first contribution in #12 **Full Changelog**: https://github.com/digidem/comapeo-core-react-native/commits/v1.0.0-pre.2 <!-- <release-meta>{"id":342868678,"version":"v1.0.0-pre.2","npmTag":"pre","opticUrl":"https://optic-zf3votdk5a-ew.a.run.app/api/generate/"}</release-meta> -->
## Optic Release Automation This **draft** PR is opened by Github action [optic-release-automation-action](https://github.com/nearform-actions/optic-release-automation-action). A new **draft** GitHub release [v1.0.0-pre.2](https://github.com/digidem/comapeo-core-react-native/releases/tag/untagged-352a6c41c12fd02dec37) has been created. Release author: @gmaclennan #### If you want to go ahead with the release, please merge this PR. When you merge: - The GitHub release will be published - The npm package with tag pre will be published according to the publishing rules you have configured - No major or minor tags will be updated as configured #### If you close the PR - The new draft release will be deleted and nothing will change <!-- Release notes generated using configuration in .github/release.yml at 7fe80b4 --> ## What's Changed ### 🚀 Features * Integrate @comapeo/core via IPC over Unix sockets by @gmaclennan in #5 * Add iOS support & test infrastructure by @gmaclennan in #6 * iOS Phase 1: unified JS bundle + smoke test (simulator-only) by @gmaclennan in #15 * iOS Phase 2: xcframework Embed & Sign for native addons by @gmaclennan in #16 * Phase 2 Android: jniLibs packaging + unified rollup loader plugin by @gmaclennan in #17 * android: read abiFilters from reactNativeArchitectures (#30) by @gmaclennan in #35 * Add rootkey persistence and lifecycle state management by @gmaclennan in #36 * Sentry integration: Phase 1 + Phase 2a + Phase 2b by @gmaclennan in #54 * feat(backend): polywasm-backed undici on iOS, re-enable maps plugin by @gmaclennan in #62 * feat(sentry): land Phase 3 — backend loader + RPC tracing by @gmaclennan in #63 * feat(sentry): land Phases 6 + 7a — Android exit reasons & iOS MetricKit app-exit telemetry by @gmaclennan in #72 * feat(sentry): migrate to @sentry/react-native v8; exit telemetry as Application Metrics by @gmaclennan in #73 * Map server integration by @gmaclennan in #86 * feat(config): let the consuming app supply the default project config by @gmaclennan in #95 ### 🐛 Bug Fixes * fix(android): drop setUnlockedDeviceRequired from rootkey wrapper key by @gmaclennan in #57 * fix(backend): cache stopping/error frames for late joiners by @gmaclennan in #58 * fix(ios-tests): wait for STOPPING before signalling node exit by @gmaclennan in #59 * fix(android): drain JNI stdio pumps before returning from node::Start by @gmaclennan in #60 * fix(ios-tests): serialise STOPPING/STOPPED observers in testFullLifecycleStateTransitions by @gmaclennan in #71 * fix(sentry): make exit telemetry lossless and stop cross-process clobbering by @gmaclennan in #84 * fix: start fastify listening by @gmaclennan in #93 * fix(ci): ignore-scripts in ios npm installs by @gmaclennan in #96 * fix(ci): replace --ignore-scripts with npm strict-allow-scripts allowlist by @gmaclennan in #106 * fix(release): stop `npm pack --dry-run` leaking dry-run into backend install by @gmaclennan in #129 ### ⚡ Performance * perf(backend): switch bundler from rollup to rolldown by @gmaclennan in #94 ### ⬆️ Dependencies * update some native deps used in backend by @achou11 in #14 * chore(deps): upgrade to Expo SDK 56 (React Native 0.85) by @gmaclennan in #87 ### 🏗️ Maintenance * Android Testing Infrastructure & Bug Fixes by @gmaclennan in #3 * chore: prebuild example/android; harden instrumented tests by @gmaclennan in #10 * chore: adjust repo setup by @achou11 in #12 * chore: minor fixes based on expo-doctor by @achou11 in #13 * chore: add architecture docs & plans by @gmaclennan in #11 * chore: post-Phase-2 cleanup — comments, plan docs, agents.md by @gmaclennan in #33 * refactor: simplify build-backend.ts; rollup writes directly to native asset trees by @gmaclennan in #34 * chore: fix eslint configuration by @achou11 in #41 * android: audit 16 KB page alignment on every shipped .so by @gmaclennan in #43 * chore: move example app into apps directory by @achou11 in #18 * refactor: per-component lifecycle state with derived ComapeoState by @gmaclennan in #47 * android: fold waitForFile into connect retry loop by @gmaclennan in #52 * chore: add e2e testing app by @achou11 in #49 * ci: drop unreliable Android emulator snapshot caching by @gmaclennan in #64 * use npm list instead of custom traversal to get native module versions by @achou11 in #70 * chore(e2e): add e2e tests on browserstack via Maestro by @achou11 in #56 * chore(ci): add release workflow by @gmaclennan in #90 * chore: fix npm script and release build script by @gmaclennan in #91 * chore(pack): don't try to package build files by @gmaclennan in #92 * chore(release): merge prerelease branch. by @gmaclennan in #110 * ci(e2e): retry BrowserStack builds on infra-class flakes by @gmaclennan in #113 ### Other Changes * ci: derive changelog labels from PR titles + add Dependabot by @gmaclennan in #114 ## New Contributors * @achou11 made their first contribution in #12 * @optic-release-automation[bot] made their first contribution in #112 **Full Changelog**: https://github.com/digidem/comapeo-core-react-native/commits/v1.0.0-pre.2 <!-- <release-meta>{"id":342970724,"version":"v1.0.0-pre.2","npmTag":"pre","opticUrl":"https://optic-zf3votdk5a-ew.a.run.app/api/generate/"}</release-meta> -->
| Back | FazBrowse Home | New Git URL |
Post-mortem visibility on OS-driven kills, answering "does our backend stay alive long enough on this user's device?" per platform.
Android (Phase 6)
ExitReasonsCollector surfaces ActivityManager.getHistoricalProcessExitReasons() records (API 30+) as "android exit: REASON_*" events on the next process start:
iOS (Phase 7a)
AppExitMetricsCollector subscribes to MXMetricPayload — the MetricKit metric side sentry-cocoa deliberately skips (their SentryMetricKitIntegration only covers the diagnostic side) — and forwards MXAppExitMetric buckets as "ios exit: <cohort>_<bucket>" events with exit.cause_class / exit.intentional tags and a stable window_id. The decode logic (AppExitDecoder) is MetricKit-free, so it compiles and is unit-tested on macOS via swift test; the MXMetricManagerSubscriber adapter is #if os(iOS).
Phase 9b.8 tier reclassification (shipped here, not deferred)
The duration-derived Android fields and the iOS per-exit duplication only flow when captureApplicationData is on; the records themselves ship at the diagnostic tier.
Divergences from plan
Tests & validation
Phase 7b (UserDefaults killed-in-background heuristic) stays in the plan as optional follow-up.
🤖 Generated with Claude Code