Skip to content

Reduce noisy JavaScript errors reported to Rollbar - #3625

Merged
tom2drum merged 11 commits into
mainfrom
claude/rollbar-noise-cleanup-110872
Aug 11, 2026
Merged

Reduce noisy JavaScript errors reported to Rollbar#3625
tom2drum merged 11 commits into
mainfrom
claude/rollbar-noise-cleanup-110872

Conversation

@tom2drum

@tom2drum tom2drum commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Description

Reduces noise in our Rollbar JavaScript error monitoring, where a large share of items came from third-party scripts, browser extensions, and abandoned wallet flows that never reach real users, drowning out real signal.

  • Capture only own-bundle uncaught errors. The early window error listener forwarded every uncaught error to Rollbar, so a public page's browser extensions, injected wallet SDKs, userscript inline scripts, CDN assets, and opaque cross-origin Script error. events all became noise. Rollbar has no positive "only my code" allowlist, so we gate at the listener: forward an uncaught error only when the browser attributes it to a script under /_next/. Genuine page crashes are unaffected — they report through AppErrorBoundary as explicit rollbar.critical calls, not this listener.
  • Single-source the ignore rules across both Rollbar instances. The error-page instance (_error.tsx) reused none of the browser instance's filtering. It now shares the window-free exception-class check and reports the page-crash error as a throwable so that check can read its exception class. It captures server-side uncaught exceptions — which carry none of the third-party window noise the browser instance fights — while leaving unhandled-rejection capture off.
  • Filter auto-translate DOM noise. Browser auto-translate / DOM-mutating extensions relocate nodes React owns, so React's commit-phase removeChild/insertBefore fails with a NotFoundError (3000+ occurrences). Matched generically via the shared DOM-exception message tail.
  • Prune rules the origin gate made redundant (injected-script / extension-origin / Script error / ResizeObserver loop), a net −165 lines.

Also includes two genuine fixes surfaced during triage: guarding the EIP-6963 announceProvider handler against a null detail (plus a capture-phase guard so wagmi's bundled mipd store is covered too), and reporting code-editor render-boundary crashes explicitly instead of relying on the silenced Monaco CDN noise.

Environment variables

None.

Minimum API version

None.

Breaking or incompatible changes

None. Note this changes what client errors are captured: after deploy, several currently-active items dominated by third-party scripts (notably Script error.) should stop arriving.

Additional information

Filtering here suppresses at ingestion. Expected WalletConnect/AppKit connect-flow expiries (proposal / pairing / session request) are dropped via ignoredMessages code rules — they recur and match generically. Separately, a handful of low-count, vendored/environmental crash items (wagmi/AppKit, helia, phoenix) were muted directly in the Rollbar dashboard rather than given code rules, since a dependency bump may retire them.

tom2drum and others added 9 commits August 10, 2026 11:20
Filter out unactionable client errors that reach Rollbar but reflect no
fault in our code:

- ResizeObserver loop notifications — a benign, deferred-to-next-frame
  browser signal with no user impact.
- Every error originating from the Monaco editor bundle we load from the
  jsdelivr CDN (worker importScripts failures, AMD `define` collisions
  with browser-extension scripts, CDN/adblock load failures), via a new
  `isMonacoCdnError` helper matching the versionless CDN path in either
  the message or any stack frame.

To keep the user-visible signal we'd otherwise lose, wire the code
editor's ErrorBoundary to report a distinct `Code editor failed to
render` message — fired only when the editor subtree actually crashes in
render — kept separate from the CDN noise so it is not self-filtered.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Generalize two more classes of unactionable client errors so future
variants do not resurface as new Rollbar items:

- Scripts injected into the page by the visitor's environment
  (userscripts, in-app browsers) that reference globals only defined in
  that environment. `isInjectedScriptError` keys on where the script
  lives — a `user-script:*` origin, or a ReferenceError from an inline
  script whose origin is the page document URL — rather than the
  specific missing global, so a new in-app browser with a different
  global name is covered without a config change.
- WalletConnect/AppKit rejecting a pending pairing when its TTL elapses
  before the user completes the connect flow (proposal / pairing /
  session request expired) — expected user behaviour surfaced from
  vendored SDK code.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A non-compliant wallet extension can dispatch `eip6963:announceProvider`
with a null or partial `detail`; dereferencing `detail.info.rdns` threw
an uncaught "Cannot read properties of null (reading 'info')". Bail when
`detail.info` is absent, matching the optional chaining already used for
`detail.provider` on the next line.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The null-detail announce events dispatched by non-compliant wallet
extensions are read unguarded not only by our own detection hook but by
wagmi's vendored `mipd` store (`providerDetail.info.uuid`), which throws
the same uncaught "Cannot read properties of null (reading 'info')" and
which we cannot patch.

Install a capture-phase `eip6963:announceProvider` listener before wagmi
config creates the mipd store. window listeners for a window-targeted
event fire in registration order, so ours runs first and
`stopImmediatePropagation()`s any event whose `detail.info` is missing,
neutralising it for mipd and every other consumer at once — a single
guard for the whole class rather than a per-consumer null check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The "Maximum call stack size exceeded" items originate from an injected
inline script (a DOM walker recursing over large list pages), attributed
to a document URL on our own origin rather than a `/_next/` bundle — the
same class as the injected-global ReferenceErrors already filtered, just
a RangeError.

Generalize `isInjectedScriptError`: match both ReferenceError and
RangeError from an inline document script, and key on same-origin rather
than an exact URL match so a client-side navigation (origin frame in the
document the script was parsed in, request URL on the current route) is
still recognised. Guard against matching our own bundle by excluding
`/_next/` assets and `.js` files.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The _error.tsx instance had global capture on and no checkIgnore, so it
re-reported, unfiltered, noise the browser instance already drops (e.g. the
AbortError in #390, captured there as an uncaught trace). Turn its global
capture off and give it a minimal, self-contained config.

Share only the window-free exception-class check (isIgnoredExceptionClass)
between the two instances — the browser predicate's bot / headless checks read
window, which is absent on the server, and keeping the shared surface narrow
stops a future browser-only rule from crashing the server render.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Auto-translate and DOM-mutating extensions move nodes React owns, so React's
commit-phase removeChild / insertBefore / replaceChild fail with a
NotFoundError. checkIgnore already drops the NotFoundError *class*, but our
error boundary re-reports it as a message (no body.trace), which the class
check can't see — so 3000+ of these leaked past it. Match the shared
DOM-exception tail via ignoredMessages, which reads the message body.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The early window 'error' listener forwarded every uncaught error to Rollbar,
so a public page's browser extensions, injected wallet SDKs, userscript inline
scripts, CDN assets, and opaque cross-origin "Script error." events all became
noise we can't act on. Rollbar has no positive allowlist for "only my code", so
gate at the listener: forward only when the browser attributes the error to a
script under /_next/. Genuine page crashes are unaffected — they report through
AppErrorBoundary as explicit rollbar.critical calls, not this listener.

Third-party packages bundled into our chunks still come through (in a prod build
they carry no source path to distinguish them); those stay on the mute track.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Now that the early listener forwards only errors originating in our own
/_next/ bundle, the rules that existed to drop foreign-origin uncaught noise
never fire — those errors are dropped before they reach Rollbar:

- isInjectedScriptError: every case it caught (user-script:*, injected inline
  on the document URL — all non-/_next/) is now gated at the listener. Removed
  it and its now-unused helper getExceptionOriginFileName.
- IGNORED_ORIGIN_FILE_NAMES_CHUNKS: chrome-extension:// is gated; the
  /node_modules/@WalletConnect|@reown paths only ever match after server-side
  source-mapping, so never matched client-side in a prod build anyway.
- ignoredMessages 'Script error' and 'ResizeObserver loop': both arrive as
  empty-filename window errors, which the origin gate drops.

Kept isMonacoCdnError (own-bundle traces that reach the CDN deeper in the
stack still pass the gate) and the bot / headless / chunk / exception-class /
message rules (they apply to explicit rollbar calls too).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@tom2drum tom2drum left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewed d082c2cc2af19527
Round 1 of 3
Findings 1 blocker · 3 major · 3 nit
By axis Spec 1 · Standards 4 · Correctness 2
Outcome blocked

Not anchorable

  • nit · Spec — PR Additional information says AppKit/wagmi items were muted in the Rollbar dashboard rather than given code rules, but clientConfig still adds 'Proposal expired' | 'Pairing expired' | 'Session request expired'. Update the PR text to own these, or drop the code rules.
  • pre-existing (out of scope)isIgnoredExceptionClass drops every NotFoundError / AbortError by class alone (same breadth as before this PR). Narrowing to the DOM-exception message is a possible follow-up, not introduced here.

Comment thread src/shared/code-editor/CodeEditor.tsx Outdated
Comment thread src/services/rollbar/serverConfig.ts
Comment thread src/features/connect-wallet/utils/installEip6963AnnounceGuard.ts Outdated
Comment thread src/features/connect-wallet/utils/install-eip6963-announce-guard.ts
Comment thread src/services/rollbar/utils.ts Outdated
Comment thread src/services/rollbar/utils.spec.ts Outdated
tom2drum and others added 2 commits August 11, 2026 11:15
Two of this change's own filters were silently discarding the reports they
were meant to preserve:

- CodeEditor reported editor render crashes by passing the Error instance, so
  Rollbar built a stack trace whose frames run through the Monaco CDN bundle —
  which isMonacoCdnError then dropped. Report the error as message data instead.
- _error.tsx reported only err.message, a payload with no exception class, so
  the shared isIgnoredExceptionClass check never fired. Pass the Error as a
  throwable so a trace with body.trace.exception.class exists; capture
  server-side uncaught exceptions, which carry no third-party window noise.

Also: rename installEip6963AnnounceGuard to kebab-case and make it a named
export; annotate isMonacoCdnError's return type; drop the Dictionary coercions
from the utils spec (plain literals satisfy the index-signature type).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`buildServerConfig` is instantiated at module scope in `_error.tsx`, which also
loads in the browser on a client-side error-page navigation. With
`captureUncaught` unconditionally on, that client-side instance installs a
`window.onerror` handler that bypasses the queue's own-bundle origin gate and
recaptures the third-party noise this change set out to drop. Enable it only on
the server, where those page-crash errors surface nowhere else.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@tom2drum tom2drum left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewed 2af19527e77233567 (arbitration of Round 1)
Round 2 of 3
Findings 0 blocker · 0 major · 1 nit
By axis Spec 1 · Standards 0 · Correctness 0
Outcome clear

Arbitration

Finding Ruling
F1 blocker · CodeEditor Error-as-throwable fixed-accepted — plain { cause, stack } data; isMonacoCdnError cannot drop it
F2 major · server checkIgnore / _error shape fixed-acceptedrollbar.error(err ?? …) builds exception.class; captureUncaught server-gated as documented
F3 major · default export fixed-accepted — named export
F4 major · camelCase filename fixed-acceptedinstall-eip6963-announce-guard.ts
F5 nit · isMonacoCdnError return type fixed-accepted: boolean
F6 nit · as unknown as Dictionary fixed-accepted — casts removed

No fix-introduced regressions. The server captureUncaught: typeof window === 'undefined' expansion is intentional (JSDoc why-comment) and out of bounds to re-litigate.

Not anchorable

  • nit · Spec (still open from R1) — PR Additional information still says AppKit/wagmi items were muted in the dashboard rather than given code rules, while clientConfig keeps 'Proposal expired' | 'Pairing expired' | 'Session request expired'. Update the PR text to own these, or drop the code rules.

@tom2drum tom2drum left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewed 2af19527e77233567 (arbitration correction)
Round 2 of 3
Findings 0 blocker · 0 major · 0 nit
By axis Spec 0 · Standards 0 · Correctness 0
Outcome clear

Correction to the prior Round 2 body: the remaining Spec nit is closed. The PR description now owns the WalletConnect expiry ignoredMessages ("Expected WalletConnect/AppKit connect-flow expiries… are dropped via ignoredMessages code rules") and separately describes dashboard mutes for low-count items — so that R1 not-anchorable finding no longer holds.

All six Round 1 findings remain fixed-accepted. No open findings.

@tom2drum
tom2drum merged commit b57dcd6 into main Aug 11, 2026
12 checks passed
@tom2drum
tom2drum deleted the claude/rollbar-noise-cleanup-110872 branch August 11, 2026 11:02
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