Skip to content

fix(dashboard): report only our own errors, not browser extensions'#565

Closed
chhhee10 wants to merge 1 commit into
mainfrom
fix/client-error-origin-filter
Closed

fix(dashboard): report only our own errors, not browser extensions'#565
chhhee10 wants to merge 1 commit into
mainfrom
fix/client-error-origin-filter

Conversation

@chhhee10

Copy link
Copy Markdown
Contributor

The problem

GlobalErrorListeners (mounted in app/layout.tsx, so it runs on every dashboard page) registers page-global handlers:

window.addEventListener("unhandledrejection", handleUnhandledRejection);

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:

event:         unhandled_rejection
error_message: Failed to connect to MetaMask
error_name:    i                              ← MetaMask's minified class
$current_url:  http://localhost:8020/policies?cli=claude
$lib:          failproofai-web                ← attributed to us

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:

  • Positive attribution, not a denylist. Only errors traceable to our origin are reported, so an extension nobody has heard of is filtered by default. A denylist would need updating forever.
  • Matched on the shared <vendor>-extension:// suffix rather than an explicit vendor list, so chrome-, moz-, safari-web-, ms-browser- and any future browser's scheme are covered without a code change.
  • A stack passing through an extension is the extension's, even when one of our frames appears further down.
  • Unattributable errors are dropped — cross-origin "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) report client_error directly, 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.ts pins 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.tsx drives real window events 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 captureClientEvent through the same mock, so "not called" means genuinely filtered.

Gate: 2113 tests / 124 files pass, 0 lint errors, clean tsc.

Notes

🤖 Generated with Claude Code

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>
@chhhee10
chhhee10 force-pushed the fix/client-error-origin-filter branch from ddad06e to de4acdd Compare July 17, 2026 10:31
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ec320d90-18a9-4203-940b-c4778bd9ec7c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@chhhee10

Copy link
Copy Markdown
Contributor Author

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.

@chhhee10 chhhee10 closed this Jul 17, 2026
@chhhee10
chhhee10 deleted the fix/client-error-origin-filter branch July 17, 2026 10:36
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