fix(browser): forward uncaught worker errors with their stack - #24210
Open
d2anamaria wants to merge 10 commits into
Open
fix(browser): forward uncaught worker errors with their stack#24210d2anamaria wants to merge 10 commits into
d2anamaria wants to merge 10 commits into
Conversation
- Add an `error` listener in `registerWebWorker` that posts `event.error` (falling back to `event.message`) over the existing `_sentryWorkerError` channel - Add optional `kind` discriminator to `SerializedWorkerError`; a missing `kind` means rejection, so workers registered by an older SDK keep working - Rename `handleForwardedWorkerRejection` to `handleForwardedWorkerError` and branch on `kind` for both the mechanism and `eventFromUnknownInput`'s `isUnhandledRejection` argument - Report forwarded throws under the `auto.browser.web_worker.onerror` mechanism - Restrict `_eventFromRejectionWithPrimitive` to rejections so a thrown primitive is not labelled "Non-Error promise rejection" - Set `Error.stackTraceLimit = 50` in the worker, matching globalHandlersIntegration, since V8's default of 10 truncates stacks before they are forwarded - Wrap the forwarding `postMessage` so a non-cloneable reason is described instead of raising DataCloneError out of the worker's error handler - Correct the doc comment claiming globalHandlers already captures sync worker errors
d2anamaria
requested review from
andreiborza,
logaretm and
msonnb
and removed request for
a team
September 8, 2026 14:45
Contributor
size-limit report 📦
|
Contributor
|
👋 @logaretm, @msonnb, @andreiborza — Please review this PR when you get a chance! |
The parent already holds the Worker object, so it listens for its error event and tells globalHandlers to skip the frameless copy that bubbles to window.onerror. The event is not cancelled, so the browser still prints its own report. The skip only applies once the worker announced that it forwards errors, so workers on an older SDK keep the bubbled event. Structured clone resets any error name outside the built-in set, so the worker sends the name separately and the parent restores it. When the reason cannot be cloned, the worker retries with a fresh Error that keeps message and stack, or with a normalized value for anything else. Message-only errors get a frame from the ErrorEvent location, the same way globalHandlers does.
The ErrorEvent filename can differ from the worker script when the error comes from an imported module, so the worker forwards it and the page builds the fallback frame from it. The single-event e2e test now waits for a second worker's event instead of sleeping. The bubbled copy of the first throw is queued right behind the forwarded one, so it would arrive before that event.
An ErrorEvent reports an unknown script as an empty string, which skipped the worker script and left the fallback frame on the page URL.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 9f65b27. Configure here.
The page suppresses the bubbled copy of every error once the worker has announced itself, so a retry that posts another Error loses the error completely in browsers that cannot clone Error at all. The retry now sends a plain message and stack copy and the page rebuilds the Error.
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.

Uncaught errors thrown inside a web worker reach Sentry without a usable stack. They bubble to the page, so an event is still created, but the propagated
ErrorEventcarries no error object, only a message string. The result is an event with a single synthetic frame pointing at the worker bundle and a value prefixed withUncaught.For plain JavaScript that degrades acceptably: one frame plus a sourcemap still locates the throw. For WebAssembly it fails outright. No frame carries a wasm URL, so the wasm integration finds nothing to match and the event ships with no debug images, even though the worker's images already reached the page. An uncaught wasm trap in a worker is unsymbolicatable today, while the identical trap wrapped in try/catch symbolicates fine.
Root cause
registerWebWorkeronly forwarded unhandled rejections, on the assumption that synchronous errors were already covered by the global handlers. They are captured, but only from the message string, because an error that crosses a worker boundary loses its error object by design.Solution
Uncaught worker errors are now forwarded to the page over the same channel that already carries rejections. Structured clone preserves
message,stackandcause, so the page receives a real error and parses a real stack, the same outcome the caught path already produced. Wasm frames then match their debug images and symbolicate normally. Forwarded errors are distinguishable from rejections by their mechanism, and the worker's stack trace limit now matches the page's so deep stacks are no longer truncated before being sent.Structured clone resets any error name outside the built-in set to
Error, which would turn a wasmRuntimeErroror a custom subclass into a plainError. The worker sends the name separately and the page restores it before building the event.The throw still bubbles to the page after the worker forwards it. The page side of the integration already holds the
Workerobject, so it listens for itserrorevent and tells the global handlers to skip the message-only copy. The event is not cancelled, so the browser still prints its own uncaught report in DevTools. The skip only applies once the worker has announced that it forwards errors, so workers registered by an older SDK version keep the bubbled event and mixed-version setups keep working.When the error value cannot be structured-cloned, for example a
WebAssembly.Exceptionor an error whosecauseholds a function, the worker retries with a fresh error that keeps the message and stack but drops thecause. Values that are not errors go throughnormalizeinstead.Errors that arrive without an error object, such as cross-origin script errors, get a frame from the
ErrorEventlocation, the same way the global handlers do.Limitations
A worker on this version paired with a page bundle on an older version still forwards, but the older page ignores the new fields and labels every forwarded error as an unhandled rejection.