Skip to content

📝 Document known has_replay timing gap for non-view RUM events (RUMS-6240) - #4942

Open
c-tshibas wants to merge 1 commit into
DataDog:mainfrom
c-tshibas:corneille.tshibasu/rums-6240-error-has-replay-race
Open

📝 Document known has_replay timing gap for non-view RUM events (RUMS-6240)#4942
c-tshibas wants to merge 1 commit into
DataDog:mainfrom
c-tshibas:corneille.tshibasu/rums-6240-error-has-replay-race

Conversation

@c-tshibas

Copy link
Copy Markdown

Motivation

Investigating RUMS-6240, a customer reported that some RUM errors have no
Session Replay recording available (has_replay unset), including cases
where a replay segment genuinely exists covering the exact timestamp of the
error. This PR documents the confirmed root cause as a code comment to
anchor a design discussion with the Session Replay team before any behavior
change is proposed -- no behavior change is included here.

Findings (pinpoints)

  • sessionContext.ts:34-40
    -- VIEW events compute has_replay from getReplayStats(view.id) (retained
    history, keyed by view). Every other event type (error/action/resource)
    instead takes a live, one-shot read of recorderApi.isRecording(), with no
    retry and no history.
  • recorderApi.ts:46-69
    -- isRecording() requires getDeflateWorkerStatus() === DeflateWorkerStatus.Initialized,
    which only resolves after the Deflate worker's async postMessage init
    handshake completes. The existing code comment there explicitly documents
    that this is an intentional false-negative bias, not an oversight.
  • datadogRecorder.ts:60-66
    -- record() (which starts DOM mutation capture and the initial full
    snapshot) is called as soon as RecorderStatus.Started, which only
    requires the Deflate Worker instance to exist.
  • deflateWorker.ts:61-67
    -- startDeflateWorker() returns the raw Worker as soon as its status is
    Loading or Initialized -- it does not wait for the init handshake.

Put together: local DOM-mutation recording (and the segment that will
eventually cover a given moment) can start strictly before isRecording()
is able to return true, because the two conditions are gated differently.
An error firing in that window gets has_replay: undefined baked in
permanently, since (unlike VIEW events) non-view events are assembled once
with no retroactive re-evaluation.

Existing test already encodes this exact contract:
sessionContext.spec.ts (should set hasReplay when recording has started (isRecording) on events)
asserts getReplayStats is never called for non-view events, and that
has_replay flips solely based on isRecording() -- confirming this isn't
an accidental oversight but a deliberate (if incompletely reasoned about)
design choice.

Prior related fix was narrower than it might appear: RUM-7694
("Keep more ReplayStats history to avoid wrongly marking views as having
no replay", commit 81193e5d7, PR #3318) only touched
packages/rum/src/domain/replayStats.ts (MAX_STATS_HISTORY bump) and only
affects the VIEW-event branch. It never touched the non-view path described
here.

Concrete evidence (anonymized): reviewing one flagged error's Session
Replay debug metadata showed the error's own timestamp matched the START
timestamp of that view's first replay segment (has_full_snapshot: true),
and that segment's [start, end] window fully contained the error. The
error still showed no Session Replay icon. This confirms the gap isn't just
theoretical -- a replay can genuinely exist covering an error's moment while
has_replay is still incorrectly unset for that specific error event.

Changes

Adds an explanatory code comment at the non-view branch in sessionContext.ts
pinpointing this known gap for future readers/fixers. No behavior change.

A first-pass fix was prototyped (a hasReplayDataForView() check bypassing
the worker-init gate, backed directly by the existing replayStats.ts map)
and does close most of the window -- but not all of it: an error firing
before the recorder has captured any record yet (not just before the
worker is initialized) would still read as has_replay: undefined. Closing
that residual gap likely needs either a short buffering delay before
finalizing a non-view event's has_replay, or moving the determination
server-side against the error's timestamp -- both of which seemed like
decisions worth surfacing to the Session Replay team before writing code,
rather than shipping unilaterally. Happy to open a follow-up PR with that
prototype if useful context for the discussion.

Test instructions

N/A -- comment-only change, no behavior affected.

Checklist

  • Tested locally (no behavior change; existing test suite unaffected)
  • Tested on staging
  • Added unit tests for this change.
  • Added e2e/integration tests for this change.
  • Updated documentation and/or relevant AGENTS.md file

Refs: RUMS-6240

🤖 Generated with Claude Code

@c-tshibas
c-tshibas requested a review from a team as a code owner August 12, 2026 13:36
@github-actions

Copy link
Copy Markdown


Thank you for your submission, we really appreciate it. Like many open-source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution. You can sign the CLA by just posting a Pull Request Comment same as the below format.


I have read the CLA Document and I hereby sign the CLA


You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

@c-tshibas c-tshibas left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have read the CLA Document and I hereby sign the CLA

@c-tshibas c-tshibas left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

recheck

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