Skip to content

Setting beforeSendSpan drops the root span's links from transactions (loses the previous_trace link) #25545

Open

Description

@arobison

Is there an existing issue for this?

I searched for "beforeSendSpan links" and "previous_trace beforeSendSpan" and found none.

How do you use Sentry?

Sentry SaaS (sentry.io)

Which SDK are you using?

@sentry/nextjs (browser client), 10.76.2. The same code is in @sentry/core 11.8.0.

SDK Version

10.76.2

Framework Version

Next.js 16.4

Steps to Reproduce

  1. Initialize the browser SDK with tracing and the default browserTracingIntegration (linkPreviousTrace: 'in-memory'), and any beforeSendSpan, even an identity function:

    Sentry.init({
      dsn,
      tracesSampleRate: 1.0,
      beforeSendSpan: (span) => span,
    });
  2. Load a page, then trigger a second page-load or navigation transaction.

  3. Look at the second transaction's contexts.trace.links.

Expected Result

contexts.trace.links holds the previous_trace link, as it does without beforeSendSpan. In general, a beforeSendSpan that returns the span unchanged leaves the transaction unchanged.

Actual Result

contexts.trace.links is missing. The sentry.previous_trace attribute survives in contexts.trace.data, but the trace view no longer links to the previous trace.

We see this in our own data. A navigation transaction that followed a page load in the same tab carries the sentry.previous_trace attribute, but its stored span has links: null.

What we traced in @sentry/core 10.76.2 (build/esm):

  • client.js, in processBeforeSend: when beforeSendSpan is set, the root span goes through beforeSendSpan(convertTransactionEventToSpanJson(event)), and the result is merged back with merge(event, convertSpanJsonToTransactionEvent(result)).
  • utils/transactionEvent.js: convertTransactionEventToSpanJson doesn't copy contexts.trace.links, and convertSpanJsonToTransactionEvent doesn't write them back.
  • utils/merge.js: with its default depth of 2, merge replaces contexts.trace whole. The links set by spanToTransactionTraceContext (utils/spanUtils.js) are lost.

Carrying links through both conversions would fix it. The other SpanJSON fields the round trip doesn't carry may be worth checking too.

Why it matters

beforeSendSpan is the only hook that sees standalone spans, such as INP, so apps that scrub data from every span have to set it. Setting it currently costs the previous-trace link on every transaction, app-wide.

Metadata

Metadata

Assignees

No one assigned

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions