fix(errors): forward originalError for unhandledrejection via NaisErrorsInstrumentation - #68
Merged
Conversation
…orsInstrumentation
Faro's built-in ErrorsInstrumentation forwards the real Error as pushError's
`originalError` option for window.onerror, but not for unhandledrejection
(registerOnunhandledrejection only passes { type, stackFrames }). With
preserveOriginalError enabled this meant a rejected promise's actual Error
instance never reached a custom beforeSend hook. Worse: because Faro's
listener registers first, it pushes an originalError-less item before the
app's own unhandledrejection handler runs, so a manual captureException call
for the same rejection got silently swallowed by Faro's dedupe.
Add NaisErrorsInstrumentation, a drop-in replacement for Faro's
ErrorsInstrumentation: window.onerror behavior matches upstream, and
unhandledrejection is the same logic (including its reason/detail fallback
chain, ported from Faro's registerOnunhandledrejection) with originalError
added. init() excludes the built-in ErrorsInstrumentation from
getWebInstrumentations() and registers this one instead, so there is exactly
one listener per event type and no dedupe race.
Idempotent (initialize() twice is a no-op) and destroy() only restores a
previously-installed window.onerror if nothing else has replaced it since.
Also documents this in the README and adds regression tests covering the
originalError forwarding, the null/plain-object reason fallback, and the
lifecycle edge cases (double-initialize, destroy after a later handler took
over window.onerror).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
sindrerh2
pushed a commit
that referenced
this pull request
Sep 11, 2026
🤖 I have created a release *beep* *boop* --- ## [0.7.0-beta.2](apm-v0.7.0-beta.1...apm-v0.7.0-beta.2) (2026-09-11) ### Bug Fixes * **errors:** forward originalError for unhandledrejection via NaisErrorsInstrumentation ([#68](#68)) ([19589cf](19589cf)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Faro's built-in
ErrorsInstrumentationforwards the actualErrorinstance aspushError'soriginalErroroption forwindow.onerror, but not forunhandledrejection(registerOnunhandledrejectiononly passes{ type, stackFrames }). This means:preserveOriginalError: true, a rejected promise's realErrornever reached a custombeforeSendhook.unhandledrejectionlistener, so it pushes anoriginalError-less item first — a subsequent manualcaptureException(error)call for the same rejection gets silently dropped by Faro'sdedupe.Fix
Add
NaisErrorsInstrumentation, a drop-in replacement for Faro'sErrorsInstrumentation:window.onerrorbehavior is unchanged (already forwardedoriginalErrorupstream).unhandledrejectionuses the same logic as Faro'sregisterOnunhandledrejection(including itsreason/detailfallback chain), withoriginalErrornow included.init()excludes the built-inErrorsInstrumentationfromgetWebInstrumentations()and registers this one instead — exactly one listener per event type, no dedupe race.initialize()is idempotent;destroy()only restores a previously-installedwindow.onerrorif nothing else replaced it since.Adversarially reviewed (GPT-5.6-Sol) and 3 findings were fixed: an incorrect reason-fallback chain, a
destroy()that could clobber a handler installed by other code later, and a missing idempotency guard.Docs
README updated to describe this behavior and recommend removing redundant app-level
unhandledrejection+captureExceptionlisteners now thatoriginalErroris included automatically.Testing
npx vitest run— 242 passednpx tsc --noEmit— cleannpx tsc -p tsconfig.build.json— clean