Skip to content

regression: skip "ResizeObserver loop" warning in Bugsnag reports#40722

Merged
dionisio-bot[bot] merged 1 commit into
release-8.5.0from
fix/resize-observer-bugsnag
May 28, 2026
Merged

regression: skip "ResizeObserver loop" warning in Bugsnag reports#40722
dionisio-bot[bot] merged 1 commit into
release-8.5.0from
fix/resize-observer-bugsnag

Conversation

@ricardogarim
Copy link
Copy Markdown
Member

@ricardogarim ricardogarim commented May 28, 2026

Proposed changes (including videos or screenshots)

While scrolling a channel, Bugsnag was being flooded with error events whose message is:

ResizeObserver loop completed with undelivered notifications.

This is not a real error - it is a benign, informational browser signal (per the W3C Resize Observer spec) dispatched as an ErrorEvent for historical reasons, which makes error trackers misclassify it as an unhandled exception. It became visible in 8.5.0 because PR #40105 virtualized the message list using virtua, whose per-item ResizeObservers fire in bursts during scroll and flood Bugsnag. (What the warning means and how it manifests is detailed in Further comments.)

This PR adds an onError callback to Bugsnag.start() that drops events whose top error message starts with ResizeObserver loop. The warning still occurs in the browser (developers can still see it in the console); we only stop reporting a benign browser signal as a crash. Maintainers of comparable virtualization libraries recommend the same: in TanStack/virtual#531 - a different library than virtua, but the same per-item ResizeObserver pattern and the same warning - a collaborator recommends ignoring/filtering this exact message rather than trying to fix it (details in Further comments).

onError(event) {
  const errorMessage = event.errors?.[0]?.errorMessage;
  if (typeof errorMessage === 'string' && errorMessage.startsWith('ResizeObserver loop')) {
    return false; // benign browser signal, not a crash
  }
}

String matching is unavoidable: the event has no usable stack (lineNumber: 0) and ErrorEvent.error is null, so the message string is the only identifier the browser exposes. startsWith('ResizeObserver loop') also covers Safari's older variant, ResizeObserver loop limit exceeded.

This is a deliberate, palliative fix. It stops the Bugsnag noise immediately with zero performance cost, but it does not remove the warning itself - the root cause is virtua's per-item ResizeObservers. We filter for now because the source-level fixes all trade performance or maintenance for it (see Further comments); a proper non-regressing fix stays open for later.

Issue(s)

CORE-2209

Steps to test or reproduce

  1. Open a channel with a few hundred messages (so the list virtualizes).
  2. Enable Bugsnag locally: start the server with BUGSNAG_CLIENT=00000000000000000000000000000000 (a dummy 000 works - requests return 401, which is fine for this test).
  3. Open DevTools -> Network, filter for notify.bugsnag.com.
  4. Scroll the message list up and down aggressively.
  • Before this change: repeated POSTs to notify.bugsnag.com carrying the ResizeObserver loop message (up to Bugsnag's per-session maxEvents: 10 cap, after which further events are dropped client-side).
  • After this change: 0 POSTs to notify.bugsnag.com for the ResizeObserver loop message during the same scroll. As a control, a genuinely thrown Error still POSTs normally - confirming the filter drops only the targeted message.

Further comments

What the warning means and how it shows up in our app

ResizeObserver delivers size-change callbacks at most once per animation frame. The browser runs the observer callbacks, then checks whether those callbacks caused new size changes; if they did and it can't deliver them within the same frame, it defers the rest to the next frame and emits this warning. In plain terms: "a resize callback changed layout, which produced more resizes than I could deliver this frame - I postponed the rest." Nothing is lost; the deferred notifications arrive on the next frame, which is why it is benign.

In our app it manifests during message-list scrolling, roughly like this:

  1. You scroll -> virtua mounts the message rows entering the viewport and unmounts those leaving it.
  2. virtua's internal ResizeObserver measures each newly mounted row.
  3. Those measurements feed back into virtua's layout (item offsets, total size, spacer height), which it adjusts - and that adjustment, happening inside the same delivery cycle, produces new size observations.
  4. When a scroll burst mounts/adjusts enough rows in one frame, the browser can't deliver them all, so it defers and fires the warning.

It is triggered two ways, both real:

  • Plain-text messages - by volume. Even with stable-height text, fast scrolling mounts many rows at once; the sheer number of first measurements plus virtua's feedback in a single frame overruns the per-frame budget. We verified this directly: a text-only channel (no images, no code blocks) still produced the warning in bulk.
  • Images / code blocks - by magnitude. A row whose height changes after it was first measured - an image finishing loading, or a code block laid out via dangerouslySetInnerHTML - forces a large re-measurement, the worst case for the loop. These amplify the rate but are not required for it.

useGetMore adds a spike at the top of the list: scrolling up loads older history and prepends a batch of messages at once, causing a burst of simultaneous mounts and measurements in a single frame.

Why filter instead of fixing the warning at the source?

We confirmed empirically (instrumenting window.ResizeObserver and attributing each warning to the observers that fired in the same frame) that the dominant generator is virtua's own internal ResizeObservers - not our code. virtua exposes no API to inject a custom/deferred observer; it reads ResizeObserver off the element's window at observe-time.

This is also what the maintainers of TanStack recommend. In TanStack/virtual#531, a TanStack Virtual collaborator recommends ignoring/filtering the warning rather than fixing it:

I would recommend just to ignore this error, fixing it will not change anything, saying from
experience as living with this in production for a few years now.

…suggesting the exact message-based filter we use here (ResizeObserver loop completed with undelivered notifications. / ResizeObserver loop limit exceeded), and explicitly noting that "patching it with requestAnimationFrame will not solve the underlying issue" - which is why we did not pursue the rAF shim below.

The related perf PR does not fix this. #40695 replaces flushSync with startTransition and wraps our useGetMore callbacks in requestAnimationFrame. Tested with and without it under an identical scripted scroll, the warning count is unchanged. As established above, the warnings come from virtua's observers, not our hook, so cleaning up our hook cannot move the count.

The obvious source-level fix degrades performance. Wrapping window.ResizeObserver so every callback defers one frame breaks the loop precondition, but adds ~16ms latency to every ResizeObserver in the app (~25 use sites incl. interactive ones like drag and composer auto-grow) to fix what is only a reporting problem. TanStack Virtual exposes exactly this as an option useAnimationFrameWithResizeObserver, but its own docs say it "typically should not be enabled" - ~16ms with no performance benefit, and the warning "usually indicates a deeper issue that should be fixed." A true upstream fix in virtua isn't readily available either.

Also, inokawa/virtua#470 discuss same thing.

In short: a no-cost stopgap now; a source-level fix stays open for when one exists that doesn't cost
performance.

Follow-up task: CORE-2264

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced error reporting by filtering out non-critical ResizeObserver loop warnings, reducing noise in error logs and notifications.

Review Change Stack

@dionisio-bot
Copy link
Copy Markdown
Contributor

dionisio-bot Bot commented May 28, 2026

Looks like this PR is ready to merge! 🎉
If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 28, 2026

⚠️ No Changeset found

Latest commit: e26f61f

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

Walkthrough

The PR modifies error boundary initialization to filter benign ResizeObserver loop warnings from Bugsnag error reporting. An onError callback is added to Bugsnag's configuration that inspects error messages and returns false for ResizeObserver-related errors, preventing them from being reported while allowing all other errors through.

Changes

Bugsnag ResizeObserver error filtering

Layer / File(s) Summary
Bugsnag ResizeObserver error callback
apps/meteor/client/views/root/OutermostErrorBoundary.tsx
An onError handler is added to Bugsnag.start that returns false for errors whose first error message starts with "ResizeObserver loop", suppressing these benign browser warnings from error reporting.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Suggested labels

type: chore

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding Bugsnag filtering to skip ResizeObserver loop warnings, which directly matches the file modification.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • CORE-2209: Request failed with status code 401

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ricardogarim ricardogarim marked this pull request as ready for review May 28, 2026 15:37
@ricardogarim ricardogarim requested a review from a team as a code owner May 28, 2026 15:37
Copy link
Copy Markdown
Member

@cardoso cardoso left a comment

Choose a reason for hiding this comment

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

Awesome description! 🥇

@ricardogarim ricardogarim added this to the 8.5.0 milestone May 28, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/meteor/client/views/root/OutermostErrorBoundary.tsx`:
- Around line 26-27: Remove the inline implementation comment about the
"ResizeObserver loop" from the OutermostErrorBoundary component and keep the
existing filter logic intact (e.g., the error-check that skips the benign
ResizeObserver message inside OutermostErrorBoundary/componentDidCatch or the
error filter function). Delete the comment block so the TSX follows the project
guideline while leaving the self-descriptive filter code (the conditional that
detects "ResizeObserver loop" or the related error-filtering function)
unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7b9c1f51-d857-4531-a4f6-a4c32f022824

📥 Commits

Reviewing files that changed from the base of the PR and between 1bad98a and e26f61f.

📒 Files selected for processing (1)
  • apps/meteor/client/views/root/OutermostErrorBoundary.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Hacktron Security Check
  • GitHub Check: 📦 Build Packages
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • apps/meteor/client/views/root/OutermostErrorBoundary.tsx
🧠 Learnings (2)
📚 Learning: 2026-03-27T14:52:56.865Z
Learnt from: dougfabris
Repo: RocketChat/Rocket.Chat PR: 39892
File: apps/meteor/client/views/room/contextualBar/Threads/Thread.tsx:150-155
Timestamp: 2026-03-27T14:52:56.865Z
Learning: In Rocket.Chat, there are two different `ModalBackdrop` components with different prop APIs. During review, confirm the import source: (1) `rocket.chat/fuselage` `ModalBackdrop` uses `ModalBackdropProps` based on `BoxProps` (so it supports `onClick` and other Box/DOM props) and does not have an `onDismiss` prop; (2) `rocket.chat/ui-client` `ModalBackdrop` uses a narrower props interface like `{ children?: ReactNode; onDismiss?: () => void }` and handles Escape keypress and outside mouse-up, and it does not forward arbitrary DOM props such as `onClick`. Flag mismatched props (e.g., `onDismiss` passed to the fuselage component or `onClick` passed to the ui-client component) and ensure the usage matches the correct component being imported.

Applied to files:

  • apps/meteor/client/views/root/OutermostErrorBoundary.tsx
📚 Learning: 2026-05-06T12:21:44.083Z
Learnt from: juliajforesti
Repo: RocketChat/Rocket.Chat PR: 40256
File: apps/meteor/client/components/CreateDiscussion/CreateDiscussion.tsx:121-149
Timestamp: 2026-05-06T12:21:44.083Z
Learning: Field wrappers in rocket.chat/fuselage-forms (Field, FieldLabel, FieldRow, FieldError, FieldHint) auto-create htmlFor/id associations, aria-describedby, and role="alert" for errors. Do not manually set htmlFor, id, aria-describedby, or role attributes when using these wrappers. This automatic wiring does not apply to plain rocket.chat/fuselage components, which require explicit ID wiring per the accessibility docs. In code reviews, prefer using fuselage-forms wrappers for form fields and verify there is no unnecessary manual ID/aria wiring in files that use these wrappers. If a component uses plain fuselage components, ensure proper id wiring as per docs.

Applied to files:

  • apps/meteor/client/views/root/OutermostErrorBoundary.tsx

Comment thread apps/meteor/client/views/root/OutermostErrorBoundary.tsx
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 1 file

Re-trigger cubic

@codecov
Copy link
Copy Markdown

codecov Bot commented May 28, 2026

Codecov Report

❌ Patch coverage is 0% with 3 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (release-8.5.0@1bad98a). Learn more about missing BASE report.

Additional details and impacted files

Impacted file tree graph

@@               Coverage Diff                @@
##             release-8.5.0   #40722   +/-   ##
================================================
  Coverage                 ?   69.91%           
================================================
  Files                    ?     3327           
  Lines                    ?   126575           
  Branches                 ?    22013           
================================================
  Hits                     ?    88498           
  Misses                   ?    34791           
  Partials                 ?     3286           
Flag Coverage Δ
e2e 59.28% <0.00%> (?)
e2e-api 46.15% <ø> (?)
unit 70.00% <ø> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@MartinSchoeler
Copy link
Copy Markdown
Member

Just for documentation sake, this PR is related to this issue

inokawa/virtua#470

MartinSchoeler
MartinSchoeler approved these changes May 28, 2026
@MartinSchoeler MartinSchoeler added the stat: QA assured Means it has been tested and approved by a company insider label May 28, 2026
@dionisio-bot dionisio-bot Bot added the stat: ready to merge PR tested and approved waiting for merge label May 28, 2026
@dionisio-bot dionisio-bot Bot merged commit 721bf61 into release-8.5.0 May 28, 2026
81 of 84 checks passed
@dionisio-bot dionisio-bot Bot deleted the fix/resize-observer-bugsnag branch May 28, 2026 16:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stat: QA assured Means it has been tested and approved by a company insider stat: ready to merge PR tested and approved waiting for merge type: chore

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants