Skip to content

🐛 fix(crash): unclean-exit marker honours visibility, sampling and reports once per session - #9

Merged
nitinstp23 merged 2 commits into
mainfrom
fix/webview-unclean-exit-marker
Sep 22, 2026
Merged

nitinstp23 merged 2 commits into
mainfrom
fix/webview-unclean-exit-marker

Conversation

@nitinstp23

Copy link
Copy Markdown
Contributor

Summary

Fixes B14-2079, B14-2080 and B14-2081 (snabbit app-webview-rum, Preact page inside the Expert Android app's WebView).

  • Heartbeat no longer re-arms a hidden page. visibilitychange: hidden cleared the marker but the 10 s heartbeat wrote it back as active. An embedded WebView keeps running timers after the host pauses it and is then destroyed without pagehide, so every routine close was reported as app_unclean_exit on the next open (~8× the app_startup rate on snabbit). The heartbeat writes the current visibility; freeze / resume clear and re-arm. The native marker's heartbeat writes the current AppState.
  • Reports follow the dead session's sampling decision. The marker records sampled; an unsampled session's exit is not filed (it exported nothing else, and the lone span escaped sessionSampleRate for 100 % of sessions).
  • One report per terminated session. The marker remembers reportedSessionId, so a session resumed within sessionTimeoutMinutes is not reported once per reopen.
  • enableUncleanExitDetection config flag: default true, on web default false inside an embedded WebView (Android wv UA token, iOS WKWebView UA without Safari/). Explicit values override the heuristic. Reported in the scout.config log.

Evidence

40/40 live app_crash spans on snabbit (scout-react 0.1.16): crash.reason=tab_terminated_without_pagehide, no exception, 6 with a lifecycle: paused last breadcrumb; 39/40 session.sampled=false; one crash.previous_session_id duplicated inside a 53 s sample. Details on the tickets.

Tests

  • crash.test.ts: hidden heartbeat stays inactive, freeze/resume, page created hidden, sampled recorded and honoured, reportedSessionId recorded and honoured, a different session still reports.
  • platform.test.ts: isEmbeddedWebView UA matrix.
  • unclean-exit-gate.test.ts: Scout.initialize arms the marker in a browser, not in a WebView, and both explicit overrides.
  • make ci green locally (316 tests).

Docs

CHANGELOG [Unreleased], docs/configuration.md (new row, alwaysCaptureErrors note), docs/ARCHITECTURE.md and README.md no longer describe the web marker as app_crash.

Rollout

snabbit runs 0.1.16, where the marker is still emitted as app_crash. They need this release plus, until they wire setWebViewBridge session adoption, the WebView default (or enableUncleanExitDetection: false) in the app-webview build.

…rts once per session

B14-2079, B14-2080, B14-2081 (snabbit app-webview). The web session
marker cleared itself on visibilitychange:hidden, but the 10 s
heartbeat wrote it back as active regardless of visibility. An embedded
WebView keeps running timers after the host pauses it and is then
destroyed without pagehide, so every routine close was reported as an
app_unclean_exit on the next open (~8x the app_startup rate in the
field). The heartbeat now writes the current visibility and the Page
Lifecycle freeze/resume events clear and re-arm the marker; the native
marker's heartbeat writes the current AppState for the same reason.

The marker also records its session's sampling decision and the last
session id it reported: an unsampled session's exit is no longer filed
(it exported nothing else and the lone span escaped sessionSampleRate
for 100 % of sessions), and a session resumed within
sessionTimeoutMinutes is reported at most once instead of once per
reopen.

New enableUncleanExitDetection flag, default true, and on web default
false inside an embedded WebView (Android wv UA token or an iOS
WKWebView UA without Safari/), where the host closes the page with no
unload signal. Docs and CHANGELOG updated; README/ARCHITECTURE no longer
describe the web marker as app_crash.
@nitinstp23
nitinstp23 requested a review from nimishgj September 22, 2026 08:51
@nitinstp23
nitinstp23 force-pushed the fix/webview-unclean-exit-marker branch 2 times, most recently from 66b6d61 to f860c6b Compare September 22, 2026 09:09
…e core too

The WebView default was applied only to the local resolved config that
gates the installer; the core resolved the raw config, so
Scout.instance.config and the scout.config log still said true inside a
WebView even though the detector was (correctly) not installed. Seen on
the emulator harness. The effective config now feeds both.
@nitinstp23
nitinstp23 force-pushed the fix/webview-unclean-exit-marker branch from f860c6b to 90fc25d Compare September 22, 2026 09:09
@nitinstp23

Copy link
Copy Markdown
Contributor Author

Verification against scout-rum-example-apps/webview-react-web

Integration build = #9 + #10 + #11 merged locally (make ci green, 345 tests), packed and installed into the harness.

Headless Chrome 153 via Playwright, 29/29 (sessionSampleRate: 100, OTLP exports intercepted and parsed):

  • Marker: written on load with sampled; visibilitychange:hidden clears it and the heartbeat keeps it cleared for 12.5 s; visible/freeze/resume re-arm; a seeded dead sampled session exports exactly one app_unclean_exit attributed to it (session.id = crash.previous_session_id), never app_crash; marker records reportedSessionId; an already-reported session and an unsampled session export nothing.
  • Android WebView UA: no marker written, a pending marker is not filed, other spans still export.
  • Array.prototype.at deleted before init: no TypeError, no error span, CLS/INP absent while TTFB/FCP/LCP report; an app error carries error.origin=app; an error whose top frame is the SDK module carries error.origin=sdk + sdk_internal, is exported once for three throws, and error.count stays at the one app error.
  • 1 s stall → one long_task (LoAF only) + one frozen_frame (1.0 s); a stall overlapping a hidden interval is dropped; an 11 s stall reports 10 s with frozen_frame.capped=true.

Real Android System WebView (emulator API 34, Chrome 113, webview-bridge-flutter mode=off, DevTools over adb), 7/7: UA has wv; Scout.instance.config.enableUncleanExitDetection === false; no marker key in localStorage; 1 s stall → one long_task + one frozen_frame; HOME → visibilityState=hidden + app_paused; return → app_resumed; no app_unclean_exit/app_crash.

Kill cycle with the marker forced on (VITE_SCOUT_UNCLEAN_EXIT=true), 6/6: first launch reports nothing; HOME → 16 s → am force-stop → relaunch: no app_unclean_exit; foreground force-stop → relaunch: exactly one; a second foreground kill of the same (resumed) session: none; never app_crash.

@nitinstp23
nitinstp23 marked this pull request as ready for review September 22, 2026 09:13
@nitinstp23
nitinstp23 merged commit ccded5d into main Sep 22, 2026
6 checks passed
nitinstp23 added a commit that referenced this pull request Sep 22, 2026
The merge of origin/main into fix/frozen-frame-background left conflict
markers in src/web/index.ts and CHANGELOG.md, and #11 was merged with
them. index.ts keeps the guarded mount() installer and passes
frozenFrameMaxMs to the long-task tracker; the CHANGELOG stacks the
#9, #10 and #11 entries under [Unreleased]. make ci green (345 tests).
@nitinstp23
nitinstp23 deleted the fix/webview-unclean-exit-marker branch September 22, 2026 10:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant