From 4176124104023539f2d1ebcc9a2cda69673a93d5 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 2 Aug 2026 13:05:34 +0000 Subject: [PATCH] fix(web): drop opaque script error exceptions in posthog Safari-only "Script error." events from email-campaign traffic have no stack frames and cannot be attributed to app code. Filter them in before_send like other known-unactionable noise, while keeping any payload that still carries frames. Co-authored-by: Tyler Dane --- .../posthog-exception-filter.util.test.ts | 27 ++++++++++++++++++- .../posthog/posthog-exception-filter.util.ts | 22 +++++++++++++++ .../web/src/auth/posthog/posthog.bootstrap.ts | 3 ++- 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/packages/web/src/auth/posthog/posthog-exception-filter.util.test.ts b/packages/web/src/auth/posthog/posthog-exception-filter.util.test.ts index 01bcbce740..cd24eb745d 100644 --- a/packages/web/src/auth/posthog/posthog-exception-filter.util.test.ts +++ b/packages/web/src/auth/posthog/posthog-exception-filter.util.test.ts @@ -3,7 +3,11 @@ import { filterPosthogBeforeSend } from "./posthog-exception-filter.util"; import { describe, expect, it } from "bun:test"; const exceptionEvent = ( - entries: Array<{ type?: string; value?: string }>, + entries: Array<{ + type?: string; + value?: string; + stacktrace?: { frames?: Array> }; + }>, ): CaptureResult => ({ uuid: "test-uuid", @@ -58,6 +62,27 @@ describe("filterPosthogBeforeSend", () => { ).toBeNull(); }); + it("drops opaque browser Script error. with no stack frames", () => { + expect( + filterPosthogBeforeSend( + exceptionEvent([{ type: "Error", value: "Script error." }]), + ), + ).toBeNull(); + }); + + it("keeps Script error. when stack frames are present", () => { + const event = exceptionEvent([ + { + type: "Error", + value: "Script error.", + stacktrace: { + frames: [{ filename: "/index.js", function: "boot" }], + }, + }, + ]); + expect(filterPosthogBeforeSend(event)).toBe(event); + }); + it("keeps ApiError exceptions", () => { const event = exceptionEvent([ { type: "ApiError", value: "Request failed with status 500" }, diff --git a/packages/web/src/auth/posthog/posthog-exception-filter.util.ts b/packages/web/src/auth/posthog/posthog-exception-filter.util.ts index a9ef9b7d9c..cea754f2a6 100644 --- a/packages/web/src/auth/posthog/posthog-exception-filter.util.ts +++ b/packages/web/src/auth/posthog/posthog-exception-filter.util.ts @@ -8,9 +8,20 @@ import { isTransientBrowserNetworkMessage } from "@web/api/util/backend-unavaila const CEFSHARP_SCANNER_MESSAGE = "Object Not Found Matching Id:1, MethodName:update, ParamCount:4"; +/** + * Browser-sanitized cross-origin `window.onerror` message. Same-origin app + * scripts (this app's `/index.js` module) report real messages + frames; + * "Script error." with no stack is almost always an extension, in-app browser, + * or other third-party script — never actionable from our code alone. + */ +const SCRIPT_ERROR_MESSAGE = "Script error."; + type ExceptionEntry = { type?: unknown; value?: unknown; + stacktrace?: { + frames?: unknown[]; + }; }; const readExceptionEntries = ( @@ -40,11 +51,22 @@ const readExceptionEntries = ( })); }; +const hasStackFrames = (entry: ExceptionEntry): boolean => { + const frames = entry.stacktrace?.frames; + return Array.isArray(frames) && frames.length > 0; +}; + const isDroppableException = (entry: ExceptionEntry): boolean => { if (typeof entry.value !== "string") return false; if (entry.value === CEFSHARP_SCANNER_MESSAGE) return true; + // Opaque cross-origin errors. Keep the rare case where frames somehow + // survived sanitization so a real stack is never discarded. + if (entry.value === SCRIPT_ERROR_MESSAGE && !hasStackFrames(entry)) { + return true; + } + // SuperTokens/browser network blips that escape as unhandledrejections. // The app already treats these as expected unavailability; capturing them // only creates noise. SuperTokens' session fetch often rejects outside our diff --git a/packages/web/src/auth/posthog/posthog.bootstrap.ts b/packages/web/src/auth/posthog/posthog.bootstrap.ts index 5b0531f064..f17f98940d 100644 --- a/packages/web/src/auth/posthog/posthog.bootstrap.ts +++ b/packages/web/src/auth/posthog/posthog.bootstrap.ts @@ -36,7 +36,8 @@ export function initPosthog(): PostHog | undefined { capture_console_errors: false, }, // Drop known-unactionable exception signatures (SuperTokens/browser - // network blips, CefSharp scanner noise) before they become issues. + // network blips, CefSharp scanner noise, opaque "Script error.") before + // they become issues. before_send: filterPosthogBeforeSend, opt_in_site_apps: true, person_profiles: "always",