Skip to content

feat(browser): Run static beforeSendSpan for INP spans - #22877

Open
logaretm wants to merge 4 commits into
developfrom
feat/inp-static-before-send-span
Open

feat(browser): Run static beforeSendSpan for INP spans#22877
logaretm wants to merge 4 commits into
developfrom
feat/inp-static-before-send-span

Conversation

@logaretm

@logaretm logaretm commented Jul 30, 2026

Copy link
Copy Markdown
Member

This fixes a beforeSendSpan gap for INP spans in the static trace lifecycle, without reintroducing v1 spans.

INP is always sent as a v2 span. When span streaming is disabled it goes through the standalone send path, which runs captureSpan. That only honors a beforeSendSpan wrapped with withStreamedSpan, so a plain callback (the one static users write, typed for v1 SpanJSON) was silently skipped for INP.

This also lets it call the preprocessSpan and processSpan hooks.

INP is always emitted as a v2 span. In static trace lifecycle it goes
through the standalone send path, which only honored a streamed
beforeSendSpan, so a plain (v1 SpanJSON) callback was silently skipped
for INP. Users opting out of span streaming could not scrub the INP
element name or route.

Split the standalone send path by callback type: a plain beforeSendSpan
now scrubs the span in its native v1 SpanJSON shape, then converts it
forward to v2 (the same one-way conversion gen_ai spans use to reach the
v2 span path). No reverse v2 to v1 conversion is introduced, and captureSpan
stays stream-only.
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size % Change Change
@sentry/browser 29.85 kB +0.12% +35 B 🔺
@sentry/browser - with treeshaking flags 28.07 kB +0.19% +51 B 🔺
@sentry/browser (incl. Tracing) 47.26 kB +0.39% +183 B 🔺
@sentry/browser (incl. Tracing + Span Streaming) 47.27 kB +0.4% +187 B 🔺
@sentry/browser (incl. Tracing, Profiling) 52.02 kB +0.38% +194 B 🔺
@sentry/browser (incl. Tracing, Replay) 86.6 kB +0.24% +205 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 76.03 kB +0.27% +204 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas) 91.32 kB +0.24% +210 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback) 103.95 kB +0.19% +192 B 🔺
@sentry/browser (incl. Feedback) 47.16 kB +0.06% +24 B 🔺
@sentry/browser (incl. sendFeedback) 34.7 kB +0.11% +37 B 🔺
@sentry/browser (incl. FeedbackAsync) 39.81 kB +0.12% +45 B 🔺
@sentry/browser (incl. Metrics) 30.93 kB +0.13% +39 B 🔺
@sentry/browser (incl. Logs) 31.14 kB +0.08% +23 B 🔺
@sentry/browser (incl. Metrics & Logs) 31.82 kB +0.09% +27 B 🔺
@sentry/react 31.66 kB +0.18% +56 B 🔺
@sentry/react (incl. Tracing) 49.52 kB +0.4% +196 B 🔺
@sentry/vue 34.94 kB +0.59% +203 B 🔺
@sentry/vue (incl. Tracing) 49.25 kB +0.41% +201 B 🔺
@sentry/svelte 29.88 kB +0.1% +29 B 🔺
CDN Bundle 31.93 kB +0.2% +63 B 🔺
CDN Bundle (incl. Tracing) 47.65 kB +0.42% +195 B 🔺
CDN Bundle (incl. Logs, Metrics) 33.49 kB +0.22% +72 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) 48.99 kB +0.35% +168 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) 72.84 kB +0.08% +55 B 🔺
CDN Bundle (incl. Tracing, Replay) 85.27 kB +0.24% +197 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 86.59 kB +0.25% +210 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) 91.05 kB +0.23% +207 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 92.36 kB +0.22% +198 B 🔺
CDN Bundle - uncompressed 95.21 kB +0.19% +173 B 🔺
CDN Bundle (incl. Tracing) - uncompressed 142.89 kB +0.45% +627 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed 99.92 kB +0.18% +173 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 146.87 kB +0.43% +627 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 224.68 kB +0.08% +173 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed 262.15 kB +0.24% +627 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 266.12 kB +0.24% +627 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 275.86 kB +0.23% +627 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 279.81 kB +0.23% +627 B 🔺
@sentry/nextjs (client) 52.1 kB +0.35% +181 B 🔺
@sentry/sveltekit (client) 47.7 kB +0.41% +191 B 🔺
@sentry/core/server 79.68 kB +0.26% +199 B 🔺
@sentry/core/browser 51.79 kB +0.4% +204 B 🔺
@sentry/node 121.4 kB +0.16% +182 B 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection) 166 B - -
@sentry/node - without tracing 84.95 kB +0.21% +176 B 🔺
@sentry/aws-serverless 93.22 kB +0.21% +195 B 🔺
@sentry/cloudflare (withSentry) - minified 198.32 kB +0.37% +731 B 🔺
@sentry/cloudflare (withSentry) 488.12 kB +0.49% +2.34 kB 🔺

View base workflow run

@logaretm
logaretm force-pushed the feat/inp-static-before-send-span branch from 7dd9dd3 to 3a52992 Compare July 30, 2026 17:43
@logaretm
logaretm marked this pull request as ready for review July 30, 2026 17:50
@logaretm
logaretm requested a review from a team as a code owner July 30, 2026 17:50
@logaretm
logaretm requested review from Lms24 and msonnb and removed request for a team July 30, 2026 17:50
Comment thread packages/core/src/tracing/spans/captureSpan.ts
logaretm added 2 commits July 30, 2026 16:26
…eforeSendSpan

The static-callback standalone path bypassed `captureSpan`, so it never emitted
the `preprocessSpan`/`processSpan` hooks. Integrations that enrich spans through
those hooks stopped running for INP when a plain `beforeSendSpan` was set, most
notably Replay no longer attaching `sentry.replay_id`.

Convert the post-callback v1 SpanJSON to the intermediate StreamedSpanJSON and
run the hooks on it before serializing, matching the streaming pipeline. Still no
reverse v2 -> v1 conversion: the user callback runs on v1 first, hooks run on the
forward-converted v2 JSON.
… standalone spans

Keep the hook/callback order consistent with `captureSpan`: run
`preprocessSpan`/`processSpan` first, then the user's v1 `beforeSendSpan`, so the
callback sees integration enrichment (e.g. Replay's `sentry.replay_id`) and can
act on it.

The hooks mutate the v2 `StreamedSpanJSON`; reflect their attribute and name
changes back onto the v1 `SpanJSON` before invoking the callback. The v1 status
is a free-form message while v2 is only `'ok' | 'error'`, so snapshot the original
v1 status and restore it rather than round-trip through the lossy v2 form.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f3d1a02. Configure here.

Comment thread packages/core/src/tracing/spans/spanJsonToStreamedSpan.ts
…version

v1 SpanJSON mirrors op/origin/profile_id/exclusive_time as top-level fields
alongside their attributes. A static `beforeSendSpan` edits the top-level field,
but the forward converter only copied `data`, so those edits were dropped from
the sent v2 span. Fold the top-level fields back into attributes (top-level wins
over the initially identical attribute), the inverse of `getSpanJSON`.
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