Skip to content

fix(core): Return same value from startSpan as callback returns#19300

Draft
s1gr1d wants to merge 7 commits intodevelopfrom
sig/promise-startspan-types
Draft

fix(core): Return same value from startSpan as callback returns#19300
s1gr1d wants to merge 7 commits intodevelopfrom
sig/promise-startspan-types

Conversation

@s1gr1d
Copy link
Member

@s1gr1d s1gr1d commented Feb 12, 2026

When using startSpan, .then() is called on the callback's return value. This creates another Promise which is a problem in for example jQuery, where the returned Promise does not have the same methods as the jqXHR object.

Closes #19242

@s1gr1d s1gr1d requested a review from isaacs February 12, 2026 14:38
@github-actions
Copy link
Contributor

github-actions bot commented Feb 12, 2026

Codecov Results 📊


Generated by Codecov Action

@github-actions
Copy link
Contributor

github-actions bot commented Feb 12, 2026

size-limit report 📦

Path Size % Change Change
@sentry/browser 25.64 kB +0.29% +72 B 🔺
⛔️ @sentry/browser - with treeshaking flags (max: 24.1 kB) 24.16 kB +0.34% +80 B 🔺
@sentry/browser (incl. Tracing) 42.45 kB +0.2% +81 B 🔺
@sentry/browser (incl. Tracing, Profiling) 47.13 kB +0.21% +97 B 🔺
@sentry/browser (incl. Tracing, Replay) 81.28 kB +0.13% +101 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 70.92 kB +0.17% +118 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas) 85.97 kB +0.13% +107 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback) 98.14 kB +0.12% +112 B 🔺
@sentry/browser (incl. Feedback) 42.36 kB +0.18% +75 B 🔺
@sentry/browser (incl. sendFeedback) 30.3 kB +0.24% +71 B 🔺
@sentry/browser (incl. FeedbackAsync) 35.31 kB +0.24% +82 B 🔺
@sentry/browser (incl. Metrics) 26.82 kB +0.31% +82 B 🔺
@sentry/browser (incl. Logs) 26.96 kB +0.32% +84 B 🔺
@sentry/browser (incl. Metrics & Logs) 27.63 kB +0.27% +74 B 🔺
@sentry/react 27.41 kB +0.29% +79 B 🔺
@sentry/react (incl. Tracing) 44.8 kB +0.19% +83 B 🔺
@sentry/vue 30.08 kB +0.24% +70 B 🔺
@sentry/vue (incl. Tracing) 44.3 kB +0.21% +91 B 🔺
@sentry/svelte 25.66 kB +0.29% +74 B 🔺
CDN Bundle 28.2 kB +0.3% +82 B 🔺
CDN Bundle (incl. Tracing) 43.31 kB +0.27% +114 B 🔺
⛔️ CDN Bundle (incl. Logs, Metrics) (max: 29 kB) 29.03 kB +0.28% +80 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) 44.13 kB +0.23% +98 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) 68.1 kB +0.12% +78 B 🔺
CDN Bundle (incl. Tracing, Replay) 80.14 kB +0.09% +72 B 🔺
⛔️ CDN Bundle (incl. Tracing, Replay, Logs, Metrics) (max: 81 kB) 81.03 kB +0.11% +85 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) 85.6 kB +0.12% +98 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 86.48 kB +0.1% +84 B 🔺
CDN Bundle - uncompressed 82.45 kB +0.29% +233 B 🔺
⛔️ CDN Bundle (incl. Tracing) - uncompressed (max: 128 kB) 128.16 kB +0.19% +233 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed 85.28 kB +0.28% +233 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 130.99 kB +0.18% +233 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 208.94 kB +0.12% +233 B 🔺
⛔️ CDN Bundle (incl. Tracing, Replay) - uncompressed (max: 245 kB) 245.04 kB +0.1% +233 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 247.86 kB +0.1% +233 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 257.84 kB +0.1% +233 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 260.65 kB +0.09% +233 B 🔺
@sentry/nextjs (client) 47.13 kB +0.19% +89 B 🔺
@sentry/sveltekit (client) 42.91 kB +0.24% +100 B 🔺
@sentry/node-core 52.24 kB +0.18% +89 B 🔺
@sentry/node 166.59 kB +0.05% +81 B 🔺
@sentry/node - without tracing 94.04 kB +0.1% +92 B 🔺
@sentry/aws-serverless 109.54 kB +0.09% +89 B 🔺

View base workflow run

@github-actions
Copy link
Contributor

github-actions bot commented Feb 12, 2026

node-overhead report 🧳

Note: This is a synthetic benchmark with a minimal express app and does not necessarily reflect the real-world performance impact in an application.

Scenario Requests/s % of Baseline Prev. Requests/s Change %
GET Baseline 9,378 - 9,468 -1%
GET With Sentry 1,603 17% 1,662 -4%
GET With Sentry (error only) 6,094 65% 6,035 +1%
POST Baseline 1,193 - 1,179 +1%
POST With Sentry 571 48% 583 -2%
POST With Sentry (error only) 1,033 87% 1,061 -3%
MYSQL Baseline 3,154 - 3,257 -3%
MYSQL With Sentry 405 13% 502 -19%
MYSQL With Sentry (error only) 2,571 82% 2,701 -5%

View base workflow run

@s1gr1d s1gr1d requested review from Lms24 February 12, 2026 15:00
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@isaacs isaacs marked this pull request as draft February 13, 2026 15:27
@isaacs
Copy link
Member

isaacs commented Feb 13, 2026

Converting to a draft, going to push some more commits to try out a slightly different approach to this.

The Remix failures are interesting. Playwright timing out is less interesting, but also could be indicative of a problem, if it's waiting for an expected failure that we're swallowing (haven't dug deeply into it yet).

s1gr1d and others added 7 commits February 15, 2026 16:31
This might be an acceptable compromise, so long as it doesn't blow up
our bundle size too much.

Copy properties from the original promise onto the chained tracker, so
that we can return something that can inspect the error, does not
obscure `unhandledrejection`, *and* supports jQuery's extended
PromiseLike objects.
@isaacs isaacs force-pushed the sig/promise-startspan-types branch from 58c695d to a729eb8 Compare February 16, 2026 00:38
@isaacs
Copy link
Member

isaacs commented Feb 16, 2026

So, there is no way to return the actual promise and also track the error, without obscuring the unhandledrejection behavior (either suppressing it on unhandled rejections, or triggering it on handled ones).

Added a commit to copy any new properties that exist on the original promise, onto the chained one. If the property is a function, then a wrapper function is added that calls the original method, bound to the original object.

This does still have the slight gotcha that any add-on methods on the original promise are now bound to it. So, this might be surprising:

const original = getDecoratedPromiseSomehow();
const returned = startSpan({..}, () => original);
const newObject = {};
returned.addedMethod.call(newObject); // surprise! runs in this-context of `original`

We can detect this and work around it easily enough if needed, but I'm thinking this is already probably more than enough support for this edge case, and if we're going any further, sniffing the this context, it seems like we'd be best off just biting the bullet with a Proxy.

Will check back in a bit to see what CI thinks of this.

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.

startSpan/startSpanManual wraps promise-like objects in a way that’s not reflected by the TypeScript type

2 participants