From 41348d5c611c4f1299560aa371810882e3ceef33 Mon Sep 17 00:00:00 2001 From: tom Date: Thu, 6 Aug 2026 19:54:17 +0200 Subject: [PATCH 1/2] Fix Rollbar empty "null or missing arguments" items MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The deferred-Rollbar refactor (#3568) added an `unhandledrejection` window listener that forwarded `event.reason` as Rollbar's sole argument. On public instances rejections are dominated by wallet-extension / third-party noise with `null`/bare-object reasons, which Rollbar cannot turn into a message — it files empty "Item sent with null or missing arguments." occurrences (Rollbar item #25, 160+ occurrences). Remove the `unhandledrejection` listener: this matches pre-refactor behavior (the app never reported unhandled rejections before) and loses nothing — genuine page crashes surface as `critical` via the React error boundary. Guard the remaining `error` listener so a non-Error/non-string thrown value becomes a titled message + the raw value as custom data, closing the same empty-item hole on that path. Co-Authored-By: Claude Opus 4.8 --- src/services/rollbar/queue.spec.ts | 29 ++++++++++++++++-------- src/services/rollbar/queue.ts | 36 +++++++++++++++++++++--------- 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/src/services/rollbar/queue.spec.ts b/src/services/rollbar/queue.spec.ts index e308eafa37f..8ea60f44781 100644 --- a/src/services/rollbar/queue.spec.ts +++ b/src/services/rollbar/queue.spec.ts @@ -152,7 +152,6 @@ describe('rollbar queue', () => { expect(rollbarInstance.warn).not.toHaveBeenCalled(); expect(errorHandler).toBeTypeOf('function'); expect(removeEventListenerSpy).toHaveBeenCalledWith('error', errorHandler, true); - expect(removeEventListenerSpy).toHaveBeenCalledWith('unhandledrejection', expect.any(Function)); }); }); @@ -175,21 +174,33 @@ describe('rollbar queue', () => { remove(); }); - it('should buffer unhandledrejection events until init', async() => { + it('should report a non-Error thrown value under a fallback message with the value as custom data', async() => { const queue = await importQueue(); const remove = queue.installEarlyListeners(); - const reason = new Error('rejected promise'); + // A synchronous `throw` of a non-Error value would otherwise reach Rollbar as a bare object + // and be filed as a generic "null or missing arguments." item (issue #3566, subtask 3). + const thrown = { code: 'BOOM' }; - window.dispatchEvent(new PromiseRejectionEvent('unhandledrejection', { - promise: Promise.resolve(), - reason, - })); - expect(rollbarInstance.error).not.toHaveBeenCalled(); + window.dispatchEvent(new ErrorEvent('error', { message: 'Uncaught object', error: thrown })); + await queue.init(); + + expect(rollbarInstance.error).toHaveBeenCalledWith( + 'Uncaught object', + { client_timestamp: CALL_TIME_S, error: thrown }, + ); + + remove(); + }); + + it('should fall back to the event message when there is no error object', async() => { + const queue = await importQueue(); + const remove = queue.installEarlyListeners(); + window.dispatchEvent(new ErrorEvent('error', { message: 'Script error.', error: null })); await queue.init(); expect(rollbarInstance.error).toHaveBeenCalledWith( - reason, + 'Script error.', { client_timestamp: CALL_TIME_S }, ); diff --git a/src/services/rollbar/queue.ts b/src/services/rollbar/queue.ts index 0203a28a98f..6f41c6fb602 100644 --- a/src/services/rollbar/queue.ts +++ b/src/services/rollbar/queue.ts @@ -133,10 +133,32 @@ function withClientTimestamp(args: Array, timestamp: number return next; } +const UNCAUGHT_ERROR_FALLBACK_MESSAGE = 'Uncaught error'; + /** - * Captures uncaught errors / unhandled rejections during (and after) the SDK deferral window. - * Kept for the page lifetime on success — removed if init fails. Rollbar's own capture flags stay - * off to avoid double-reporting. + * Coerces a thrown value into arguments Rollbar can build an occurrence from. Passed a bare + * non-Error object (or `null`) as its sole argument, Rollbar discards the payload and files a + * generic "Item sent with null or missing arguments." occurrence — so anything that is not an + * `Error` or `string` is reported under {@link UNCAUGHT_ERROR_FALLBACK_MESSAGE} with the raw value + * preserved as custom data. + */ +function toReport(value: unknown, message: string): Array { + if (value instanceof Error || typeof value === 'string') { + return [ value ]; + } + if (value === null || value === undefined) { + return [ message || UNCAUGHT_ERROR_FALLBACK_MESSAGE ]; + } + return [ message || UNCAUGHT_ERROR_FALLBACK_MESSAGE, { error: value } ]; +} + +/** + * Captures uncaught errors during (and after) the SDK deferral window. Kept for the page lifetime + * on success — removed if init fails. Rollbar's own `captureUncaught` stays off to avoid + * double-reporting; `captureUnhandledRejections` is left off deliberately — on public instances + * unhandled rejections are dominated by wallet-extension / third-party noise with no usable + * payload (they file empty "null or missing arguments" items), and genuine page crashes surface as + * `critical` through the React error boundary, not here. */ export function installEarlyListeners(): () => void { if (!isEnabled() || earlyListenersInstalled || typeof window === 'undefined') { @@ -152,19 +174,13 @@ export function installEarlyListeners(): () => void { if (event.target instanceof Element) { return; } - log('error', [ event.error ?? event.message ]); - }; - - const handleRejection = (event: PromiseRejectionEvent) => { - log('error', [ event.reason ]); + log('error', toReport(event.error, event.message)); }; window.addEventListener('error', handleError, true); - window.addEventListener('unhandledrejection', handleRejection); const uninstall = () => { window.removeEventListener('error', handleError, true); - window.removeEventListener('unhandledrejection', handleRejection); earlyListenersInstalled = false; if (uninstallEarlyListeners === uninstall) { uninstallEarlyListeners = undefined; From d30429af89b0327bbe9de446ebe2d8ce67348aa0 Mon Sep 17 00:00:00 2001 From: tom Date: Thu, 6 Aug 2026 20:02:19 +0200 Subject: [PATCH 2/2] Ignore opaque cross-origin "Script error." reports in Rollbar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The uncaught-error window listener added in #3568 surfaces cross-origin "Script error." events — the browser masks details of errors thrown by different-origin third-party scripts (GA, walletconnect, etc.), leaving only that string with no stack or payload. They are unactionable and were never reported before the deferred-Rollbar refactor. Add "Script error" to `ignoredMessages` (the existing 'cross-origin' entry does not match this literal string), dropping the noise while the listener still captures genuine same-origin uncaught errors. Co-Authored-By: Claude Opus 4.8 --- src/services/rollbar/clientConfig.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/services/rollbar/clientConfig.ts b/src/services/rollbar/clientConfig.ts index ecfaafda4f3..3567a6b161f 100644 --- a/src/services/rollbar/clientConfig.ts +++ b/src/services/rollbar/clientConfig.ts @@ -76,6 +76,11 @@ export function buildClientConfig(accessToken: string): Configuration { // Filter out client-side navigation cancellations 'cancelled navigation', + + // Opaque cross-origin script errors: the browser masks the details of an uncaught error + // thrown by a different-origin script (CORS), leaving only this string with no stack or + // payload. Unactionable, and sourced from third-party scripts we don't control. + 'Script error', ], maxItems: 10, // Max items per page load // uncaught / unhandledrejection coverage is owned by the early window listeners in queue.ts —