feat(browser): Run static beforeSendSpan for INP spans - #22877
Open
logaretm wants to merge 4 commits into
Open
Conversation
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.
Contributor
size-limit report 📦
|
logaretm
force-pushed
the
feat/inp-static-before-send-span
branch
from
July 30, 2026 17:43
7dd9dd3 to
3a52992
Compare
logaretm
marked this pull request as ready for review
July 30, 2026 17:50
…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.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ 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.
…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`.
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.

This fixes a
beforeSendSpangap 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 abeforeSendSpanwrapped withwithStreamedSpan, so a plain callback (the one static users write, typed for v1SpanJSON) was silently skipped for INP.This also lets it call the
preprocessSpanandprocessSpanhooks.