[Replay][Safari] Visual state freezes around the 60-second error-buffer checkout while non-DOM events continue
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 42/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Khá rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- angular, typescript
- Lĩnh vực
- frontend, observability
Hướng nghiên cứu
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.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
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.
- Ngôn ngữ chính
- TypeScript
- Star
- 8.7k
- Fork
- 1.9k
- Merge trung bình
- 1 ngày 18 giờ
- Pull request đã merge (30 ngày)
- 562
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của getsentry/sentry-javascript
-
Browser Waiting for: Product Owner
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 85/100
getsentry/sentry-javascript#24577 · 1 bình luận ·
-
Task
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
getsentry/sentry-javascript#24558 · 1 bình luận ·
-
Task
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 90/100
getsentry/sentry-javascript#24557 · 1 bình luận ·
-
Task
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 84/100
getsentry/sentry-javascript#24556 · 1 bình luận ·
-
Task
Độ khó 1/5 1-3 giờ Mức phù hợp với người mới 90/100
getsentry/sentry-javascript#24555 · 1 bình luận ·
Tất cả issue của getsentry/sentry-javascript
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 65/100
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
-
bug v2
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
modelcontextprotocol/inspector#2458 · 1 bình luận ·
-
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 75/100
railmapgen/rmp-gallery#4068 ·
-
Mend: dependency security vulnerability status: needs triage 🕵️♀️
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
carbon-design-system/ibm-products#9907 ·