[Replay][Safari] Visual state freezes around the 60-second error-buffer checkout while non-DOM events continue
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 42/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- angular, typescript
- Domain
- frontend, observability
Research direction
Start at the Replay integration's 60,000 ms error-buffer checkout and the rrweb full-snapshot serializer path described in the report; reproduce in Safari if possible and preserve the original exception while tracing the lock, snapshot, and unlock sequence. Done means a failed checkout cannot leave DOM mutation recording locked, opaque global rejections are contained, and later visual state remains consistent with the Replay timeline.
Written by the indexing model from the issue text.
Description
Is there an existing issue for this?
- I have checked for existing issues https://github.com/getsentry/sentry-javascript/issues
- I have reviewed the documentation https://docs.sentry.io/
- I am using the latest SDK release https://github.com/getsentry/sentry-javascript/releases
How do you use Sentry?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/angular
SDK Version
10.75.0
Framework Version
Angular: 22.1.7
Link to Sentry event
Reproduction Example/SDK Setup
We do not currently have a deterministic public reproduction. The issue has been observed across multiple production Replays in Safari on macOS.
The relevant SDK setup is equivalent to:
Sentry.init({
integrations: [Sentry.browserTracingIntegration()],
replaysSessionSampleRate: 0,
replaysOnErrorSampleRate: 1.0,
});
Sentry.addIntegration(
Sentry.replayIntegration({
maskAllText: true,
maskAllInputs: true,
blockAllMedia: true,
beforeErrorSampling: (event) => {
if (!event.message) {
return true;
}
return !ignoredMessages.some((message) =>
event.message?.includes(message),
);
},
}),
);
The Replay integration is added after the Angular application has bootstrapped. Additional block, unblock, and unmask selectors have been omitted because they only target ordinary application elements.
The application uses Angular 22.1.7 without zone.js.
Steps to Reproduce
This is not yet deterministically reproducible outside production. The following sequence describes the behavior observed in affected Replays:
- Configure Replay for error-triggered buffering with
replaysSessionSampleRate: 0andreplaysOnErrorSampleRate: 1.0. - Open the Angular SPA in desktop Safari on macOS.
- Navigate within the SPA and interact with a page containing normal dynamic DOM activity.
- Keep the page open beyond the first 60-second Replay checkout interval.
- Around that boundary, observe opaque
unhandledrejectionevents in Sentry. - Continue using the application and navigate to another SPA route.
- Open the resulting Replay.
- Compare the Replay’s current URL and activity timeline with its rendered viewport.
In a representative Replay:
- The first opaque rejection occurred approximately 61.8 seconds after Replay began.
- Another
CustomEventrejection occurred approximately 60.1 seconds later. - Subsequent navigation, network, console, WebSocket, media-session, and application diagnostic activity continued.
- The rendered Replay viewport remained permanently stuck on the earlier page.
Most of the affected Replays we manually reviewed show the same frozen-visual-state pattern.
Expected Result
A failure during a periodic Replay checkout should be contained within Replay.
Specifically:
- No Replay-internal failure should escape as a global unhandled promise rejection.
- A failed full snapshot should not leave DOM mutation recording permanently locked or disabled.
- Replay should recover, retry, or stop recording cleanly.
- Subsequent DOM changes and SPA navigation should remain visually consistent with the Replay URL and activity timeline.
- The originating exception and operation should be retained with an actionable stack.
- One internal failure should not create multiple opaque Sentry issue groups.
Actual Result
Around the point where the Replay visual state stops updating, Sentry captures:
CustomEvent: Event CustomEvent (type=unhandledrejection) captured as promise rejection
and:
UnhandledRejection: Object captured as promise rejection with keys: [object has no keys]
A correlated error appears shortly afterward as a sentry.event breadcrumb:
TypeError: Cannot call a class as a function
The originating TypeError does not have a useful stack. The top-level events contain only the global unhandledrejection capture path.
After the failure:
- The application continues operating normally.
- The Replay URL changes when the user navigates.
- Network, console, WebSocket, media-session, and application diagnostic events continue to be recorded.
- The rendered Replay viewport remains permanently stuck on an earlier page.
- No corresponding user-facing crash, disconnection, or navigation failure has been identified.
We reviewed additional Replays belonging to the same Sentry issue. Most of the reviewed Replays show this same mismatch between continuing application activity and a stale visual frame.
Additional Context
Observed scope
All sampled occurrences in this issue group were from Safari on macOS.
One representative affected Replay used Safari 26.6.2. This does not mean the issue reproduces on every Safari installation or every installation of Safari 26.6.2. The exact browser, DOM, and timing conditions required to trigger the failure remain unknown.
The same error group has appeared across multiple application releases.
Suspected Replay checkout path
This is a hypothesis based on the timing, the frozen visual state, and inspection of the bundled Replay/rrweb source. We have not confirmed the exact throwing statement.
In error-buffer mode, Replay uses a 60,000 ms checkout interval. The observed failures occur close to that boundary and can repeat at approximately 60-second intervals.
The examined full-snapshot path appears to perform the following sequence:
lock mutation buffers
serialize the document
emit the full snapshot
unlock mutation buffers
The lock/snapshot/unlock sequence does not appear to protect the unlock operation with try/finally.
If Safari throws while the document is being serialized, the mutation buffers could remain locked. This would be consistent with the observed behavior: later DOM mutations disappear from the Replay while navigation, console, network, and other non-DOM events continue.
The rrweb callback wrapper also appears to mark internal errors and rethrow them. Replay then filters internal rrweb errors from normal capture. This may explain why the potentially useful TypeError is not reported with its original stack, while a downstream CustomEvent or empty object reaches the global promise-rejection handler.
The exact Safari DOM object or serialization operation that produces TypeError: Cannot call a class as a function has not been identified because the originating stack is unavailable.
User impact
No application malfunction has been observed. The current impact is on observability:
- Repeated noisy error events are generated.
- Multiple opaque issue groups may represent the same underlying failure.
- Replay becomes incomplete or misleading.
- Later user actions cannot be reconstructed visually even though their accompanying activity remains present.
Related issues reviewed
We searched open and closed issues. The closest reports do not describe the same behavior:
- https://github.com/getsentry/sentry-javascript/issues/18590 — an rrweb DOM-node failure, but with a different exception and mutation-processing path.
- https://github.com/getsentry/sentry-javascript/issues/8885 — related to Replay/full-snapshot lifecycle, but not this periodic Safari failure.
- https://github.com/getsentry/sentry-javascript/issues/7085 — describes the application itself becoming unresponsive; here only the recorded visual state freezes.
- https://github.com/getsentry/sentry-javascript/issues/6946 — describes user-facing performance degradation rather than stale Replay recording.
The automatically suggested issue #17873 concerns beforeErrorSampling behavior and does not describe DOM recording becoming frozen.
Questions
Could the Replay team advise:
- Can the full-snapshot path guarantee mutation-buffer unlocking with
try/finallyif document serialization throws? - Can Replay recover, retry, or stop cleanly after an unsuccessful periodic checkout?
- Can an internal rrweb exception be prevented from escaping as an opaque global promise rejection?
- Is there a supported diagnostic option that preserves the original rrweb exception and stack without recursively capturing Replay’s own errors?
- Are there known Safari DOM values or Web APIs that can produce
Cannot call a class as a functionduring a full snapshot? - Would any additional debug logging be useful for identifying the precise serializer operation?
Priority
React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it.
- Dominant language
- TypeScript
- Stars
- 8.7k
- Forks
- 1.9k
- Avg merge
- 1d 18h
- Merged PRs (30d)
- 543
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from getsentry/sentry-javascript
-
Browser Waiting for: Product Owner
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
getsentry/sentry-javascript#24577 · 1 comment ·
-
Task
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
getsentry/sentry-javascript#24558 · 1 comment ·
-
Task
Difficulty 1/5 Under an hour Newbie friendliness 90/100
getsentry/sentry-javascript#24557 · 1 comment ·
-
Task
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
getsentry/sentry-javascript#24556 · 1 comment ·
-
Task
Difficulty 1/5 1-3 hours Newbie friendliness 90/100
getsentry/sentry-javascript#24555 · 1 comment ·
All issues in getsentry/sentry-javascript
Similar issues
-
S: triage
Difficulty 1/5 Under an hour Newbie friendliness 85/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
fix(errors): EHOSTUNREACH from a happy-eyeballs connect is reported as a resolver error (STAMP-80) Open
Difficulty 2/5 1-3 hours Newbie friendliness 90/100
snapshot-labs/stamp#666 ·
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
GauravKarakoti/SecureFlow#1070 · 1 comment ·
-
comp/desktop P3 type/bug
Difficulty 1/5 Under an hour Newbie friendliness 92/100
NousResearch/hermes-agent#118866 ·