fix(dashboard): report only our own errors, not browser extensions'#565
fix(dashboard): report only our own errors, not browser extensions'#565chhhee10 wants to merge 1 commit into
Conversation
GlobalErrorListeners registers page-global error/unhandledrejection handlers. Browser extensions inject content scripts into the same page and share the same window, so their failures reached our listeners and went to PostHog stamped $lib: failproofai-web, as though the dashboard had thrown them. Observed live: MetaMask's "Failed to connect to MetaMask" (error_name "i", its minified class) arriving as a failproofai unhandled_rejection on /policies. Extensions are user-installed and open-ended, so the noise was unbounded and depended on which extensions a user happens to run rather than on our code. Left alone it would drown the real signal these listeners exist to catch, and make the dashboard's error rate a function of users' browser setups. Both handlers now attribute an error before reporting it (new lib/error-origin.ts) and report only what traces back to our own origin: - Positive attribution, not a denylist of known extensions, so an unrecognised extension is filtered by default. - The match is on the shared `<vendor>-extension://` suffix, so a new browser's scheme is covered without a code change. - A stack passing through an extension is treated as the extension's, even when one of our frames appears further down. - Unattributable errors (cross-origin "Script error.", rejections of non-Error values) are dropped — there is nothing in them to debug. React render errors are unaffected: the error boundaries report client_error directly, and React only invokes those for errors thrown inside our own tree. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ddad06e to
de4acdd
Compare
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
|
Folded into #560 — same commit, unchanged. One PR is easier for a small team to review; the three fixes are kept as separate commits there so they can still be reviewed one at a time. |
The problem
GlobalErrorListeners(mounted inapp/layout.tsx, so it runs on every dashboard page) registers page-global handlers:Browser extensions inject content scripts into the same page and share the same
window. Their failures therefore reached our listeners and went to PostHog stamped$lib: failproofai-web— as though the dashboard had thrown them.Observed live, which is what prompted this:
MetaMask is just the one that got noticed; ad blockers, password managers and every other injected extension do the same. There was no filter of any kind — no origin check, no stack inspection.
This matters beyond tidiness: the noise is unbounded and unrelated to our code, it makes the dashboard's error rate a function of whichever extensions users happen to run, and left alone it would drown the real signal these listeners exist to catch.
The fix
Both handlers now attribute an error before reporting it (new
lib/error-origin.ts), and report only what traces back to our own origin.Design decisions worth reviewing:
<vendor>-extension://suffix rather than an explicit vendor list, sochrome-,moz-,safari-web-,ms-browser-and any future browser's scheme are covered without a code change."Script error."and rejections of non-Error values carry nothing to debug.React render errors are unaffected. The error boundaries (
app/global-error.tsx,app/components/error-fallback.tsx) reportclient_errordirectly, and React only invokes those for errors thrown inside our own tree. These global listeners are a catch-all net, which is why they can be filtered strictly without losing genuine signal.Tests
21 new tests across two files.
__tests__/lib/error-origin.test.tspins the classifier (including the exact production MetaMask stack, all four browser schemes, an unknown future vendor, mixed stacks, and each unattributable case).__tests__/components/global-error-listeners.test.tsxdrives realwindowevents through the mounted component, so the wiring is proven rather than just the pure function.The negative assertions aren't vacuous: the positive tests fire
captureClientEventthrough the same mock, so "not called" means genuinely filtered.Gate: 2113 tests / 124 files pass, 0 lint errors, clean
tsc.Notes
main.Promise.reject("boom")) are no longer reported, as they can't be attributed. Rare, and the error boundary path still covers React errors.🤖 Generated with Claude Code