Skip to content

fix(rum): report an unattributed replayed crash in a session of its own - #84

Merged
Fiona2016 merged 3 commits into
mainfrom
fix/orphan-crash-own-session
Nov 10, 2026
Merged

Fiona2016 merged 3 commits into
mainfrom
fix/orphan-crash-own-session

Conversation

@Fiona2016

@Fiona2016 Fiona2016 commented Nov 9, 2026 •

Copy link
Copy Markdown
Collaborator

What

A crash, native crash or app freeze replayed on the next launch that cannot be attributed to the run it happened in (no original session id, and no usable last-view snapshot — typically a fault during launch before the first view, or an incident past the intake's 24 h window) was injected into the current session whenever that session was collected: written at the current time, counted in whichever view was current. That session did not crash, but read as crashed. With a sample rate of 100 every such crash took this path. Only a withheld (sessionOnError) current session turned it away to the standalone session added in 0.7.9.

Now such a crash always goes to the standalone session written by writeStandaloneCrash (one ApplicationLaunch view counting the crash + the is_crash error, real crash time unless past 24 h, ids derived from the incident, view before error, incident deleted only when both land; mapper and consent handling unchanged). The current session is never touched.

Sampling of the standalone session

Drawn independently, as a new session would be at that moment. RumApplicationScope.drawDetachedSession reuses the resolution startNewSession uses (extracted as resolveNow() / carried()): console rate or init value, console sessionOnError, the beforeSampling hook, forced sessions. It does not touch lastResolved, onSessionStarted or the current session.

  • drawn by the rate → ordinary session: no session.sampled_for_error, _dd.configuration.session_sample_rate = drawn rate, rc_version as usual;
  • missed but sessionOnError → session.sampled_for_error: true, rate 0 (the 0.7.9 behaviour);
  • missed, no sessionOnError → not reported, marked handled (as sampling did before).

The coin comes from the incident (a hash of its identity), so a retry after a partial write draws the same way. Otherwise, at a fractional rate, a retry could drop an incident whose view had already landed.

Removed (dead after this change)

RumRawEvent.inherited, refusedBy, countInView, the inherited-error branch in RumSessionScope, and the timestampMs / countInView / inherited parameters and return value of DefaultRumMonitor.reportError. No other caller used them.

Known limitation

Such a crash carries the current launch's application version, OS, user and connectivity. Nothing in the incident says what the crashed run's were.

Tests

node scripts/unit-node/run.mjs — all pass.

New: aLaunchCrashNeverJoinsATrackedCurrentSession, aLaunchCrashLeavesAReleasedCurrentSessionAlone, aSessionOfItsOwnIsDrawnLikeANewSessionNotLikeTheCurrentOne, aSessionOfItsOwnIsDrawnUnderTheConsoleSettings, aLaunchCrashWithNoSessionDrawnAndNoSwitchIsNotReported, aRetryAtAFractionalRateDrawsTheSameWay.

Updated because they encoded the old behaviour:

  • aLaunchCrashStillJoinsACollectedSessionOrFollowsSampling was replaced by the tests above;
  • aPreviousLaunchCrashInAViewGapStaysOffThisLaunchsViews now expects the crash in its own ApplicationLaunch view;
  • aRetryAfterAPartialWriteRewritesTheSameDocuments now also runs with a tracked draw;
  • comment-only updates in aCrashReplayedFromTheLastViewCarriesTheCrashedRunsContext and aFaultOlderThanTheLastViewIsNotAttributedToItsRun.

Negative control: against the unfixed source, the new and updated tests fail (6 failures). With a random coin instead of the incident-derived one, aRetryAtAFractionalRateDrawsTheSameWay fails.

This is the change on main for the 0.7.10 patch release. There is no version bump here; the fix is recorded under ## 0.7.10 in flashcat-rum/CHANGELOG.md.

Also: a crash before the first view of its session

A JS crash in a session that had not opened a view yet left an incident naming the session but no view. The replay wrote the error into that session, which had no view document — and the backend builds a session only from view documents, so the crash belonged to a session that never appeared (and a session kept on error released nothing it could join).

Before anything snapshots the RUM context for a JS fault, a session that has never had a view now opens the ApplicationLaunch view (RumSessionScope.openLaunchView, dispatched from RumFeature.reportObservedError ahead of the crash-module callback). The incident names that view, markCrash counts the crash on it, the replay joins it, and a withheld session releases it with the crash. Live crashes (REPORT_AND_CONTINUE, or no crash module) and OBSERVE_ONLY errors are written in it instead of as viewless errors.

It mints no session, is not the view a later session restores, and the app's first view replaces it. A session between two views keeps counting on its last view; a crash with no session at all still goes to the session of its own above.

Tests (rum-crash-before-first-view): persisted crash names and is counted on the launch view; withheld session releases it with sampled_for_error; live crash written in it and replaced by the first view; a second fault opens no second launch view; the replay joins it without counting again; no launch view between two views, with no session, or in an unsampled session. Negative control: against the unfixed source the three behaviour tests fail. scripts/ci-check.sh passes.

Review follow-up

  • A session that expired (15 minutes idle) without ever having a view no longer receives the crash: openLaunchView withdraws its id before the crash feature snapshots the context, so the crash is reported as one with no session of its own (a session drawn for it), and the expired session discards what it held instead of releasing it. The crash feature now reads session.sampled only while a session id is published, so a crash after an unsampled session ended is drawn instead of dropped as sampled out. Tests: aSessionThatExpiredWithoutAViewGivesTheCrashUp (with a one-minute negative control) and two crash-feature tests; against the unfixed source the two behaviour tests fail.

A crash, native crash or freeze replayed on the next launch that nothing
attributes to the run it happened in (no original session, no usable
last-view snapshot) fell back to the current session whenever that session
was collected: written at the current time, counted in whichever view was
current, so a session the crash did not happen in read as crashed. With a
sample rate of 100 every such crash took this path. Only a withheld
(on-error) current session turned it away to a standalone session.

Such a crash now always goes to the standalone session written by
writeStandaloneCrash (one ApplicationLaunch view plus the error, at the real
crash time, ids derived from the incident), and the current session is never
touched. The standalone session is drawn independently, as a new session
would be at that moment: RumApplicationScope.drawDetachedSession reuses the
resolution startNewSession uses (console rate or init value, console
sessionOnError, beforeSampling hook, forced sessions). Drawn by the rate it is
an ordinary session with the drawn rate and rc_version; kept only by
sessionOnError it is marked sampled_for_error with rate 0; otherwise the crash
is dropped as sampling drops it and marked handled. The coin is derived from
the incident, so a retry after a partial write draws the same way.

The inherited-error plumbing (RumRawEvent.inherited, refusedBy, countInView
and the matching reportError parameters) is removed: nothing reports an error
of a previous run into the scope tree any more.

Tests: new cases for a tracked current session, a released on-error session,
independent draws through the hook, console settings, no draw without the
switch, and retry stability at a fractional rate. Updated tests that encoded
the old behaviour: aLaunchCrashStillJoinsACollectedSessionOrFollowsSampling
(replaced), aPreviousLaunchCrashInAViewGapStaysOffThisLaunchsViews, and the
retry test now also runs with a tracked draw.
… view

A JS crash in a session that had not opened a view yet left an incident
naming the session but no view. The next launch replayed the error into
that session, which had no view document, and the backend builds sessions
only from view documents: the crash belonged to a session that never
appeared, and a session kept on error released nothing it could join.

Before anything snapshots the RUM context for a JS fault, the session now
opens the ApplicationLaunch view if it has never had a view. The incident
names that view, markCrash counts the crash on it, the replay joins it,
and a withheld session releases it with the crash. Live crashes
(REPORT_AND_CONTINUE, or no crash module) and OBSERVE_ONLY errors are
written in it instead of as viewless errors. It is not remembered as the
last view, mints no session, and the app's first view replaces it. A
session between two views keeps counting on its last view.
A session that expired (15 minutes idle) without ever opening a view still
published its id when a JS fault arrived. The crash incident named it, the
fault's own events then expired it with no view document, and the replayed
error went into a session that never appears. An unsampled session that had
ended left session.sampled=false behind, so a crash after it was dropped as
sampled out although it belonged to no session.

openLaunchView now withdraws the id of a session that expired without a view
before the crash feature snapshots the context, so the crash takes the path
of a crash with no session of its own (a session drawn for it). That expired
session discards what it held instead of releasing it for a crash that is
not its own. The crash feature takes session.sampled into account only while
a session id is published.
@Fiona2016
Fiona2016 merged commit b5aa0f8 into main Nov 10, 2026
6 checks passed
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