Skip to content

Span streaming drops continued traces without baggage: empty frozen DSC leads to missing_dsc (breaks Uptime check traces) #25243

Open

Description

@reichhartd

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/node

SDK Version

11.6.0

Framework Version

Node.js 24.21.0 (first seen in production with @sentry/nestjs 11.0.0 on Fastify and @sentry/react-router 11.0.0)

Link to Sentry event

No response

Reproduction Example/SDK Setup

import http from 'node:http';
import * as Sentry from '@sentry/node';

Sentry.init({
  dsn: 'https://public@o123.ingest.de.sentry.io/1',
  tracesSampleRate: 1,
  transport: (options) => Sentry.createTransport(options, () => Promise.resolve({ statusCode: 200 })),
});
Sentry.getClient().on('beforeEnvelope', ([header, items]) => {
  if (items.some(([item]) => item.type === 'span')) console.log(header.trace ? 'trace header present' : 'NO trace header');
});

const server = http.createServer((req, res) => res.end('ok'));
server.listen(0, async () => {
  await fetch(`http://127.0.0.1:${server.address().port}/`, {
    headers: { 'sentry-trace': `${'a'.repeat(32)}-${'b'.repeat(16)}-1` },
  });
  await Sentry.flush(2000);
  server.close();
});

Steps to Reproduce

  1. Run the script above with @sentry/node 11.6.0 (default traceLifecycle: 'stream').
  2. It sends one request to its own server with a sampled sentry-trace header and no baggage header.
  3. Look at the logged envelope headers: the envelope with the incoming request's spans has no trace header. (The second envelope belongs to the outgoing fetch, which starts its own trace.)
  4. Add strictTraceContinuation: true to Sentry.init and run it again: now that envelope has a trace header.

Expected Result

Spans of a request that continues a trace from a sentry-trace header without baggage reach Sentry, as the equivalent transactions did with SDK 10. For example the SDK could fill the DSC from the client (trace_id, public_key) in stream mode, Relay could accept such span envelopes, or the uptime checker could send baggage.

Actual Result

The span envelope of the incoming request goes out without a trace header, and Relay drops it. The org stats show those spans with outcome invalid, reason missing_dsc.

In production this hits Sentry Uptime monitoring: an uptime check on a readiness endpoint (trace sampling on) reaches two Node services every five minutes and gets a 200, but since the upgrade every span of those requests is dropped as missing_dsc (12 spans per check in one project, 14 in the other). A request to the same endpoint without trace headers is traced and accepted end to end. The check's requests apparently carry sentry-trace without Sentry baggage.

Additional Context

Where it comes from, in @sentry/core 11.6.0:

  • propagationContextFromHeaders (build/esm/utils/tracing.js) returns dsc: dynamicSamplingContext || {} when the traceparent has a trace id but no DSC came with it ("we are not head of trace and we must freeze it").
  • createStreamedSpanEnvelope (build/esm/tracing/spans/envelope.js) adds the trace envelope header only when dscHasRequiredProps(dsc) holds, which needs trace_id and public_key.

Workaround: strictTraceContinuation: true. The spans arrive again, but the services no longer continue the uptime check's trace, so the check and its downstream spans end up in separate traces.

Priority

No response

Activity

  1. linear-code commented on Oct 9, 2026

    @linear-code
  2. self-assigned this
    on Oct 12, 2026
  3. Lms24 commented on Oct 12, 2026

    @Lms24
    Member

    Hey @reichhartd thanks for reporting this! It appears this is a bug in our ingestion service Relay, rather than v11 of the SDK. In fact, v11 "accidentally" fixed a bug we had in v10 that caused a trace header to be sent along, even if an incoming request has no baggage header. As per Sentry's DSC spec, not sending a trace envelope header in this specific case is expected behaviour (i.e. we "freeze" an empty DSC onto the service span we continue the trace from.

    I will discuss this with the Ingest team to see if we need to make adjustments in Relay, or if we need to fix this SDK-side.
    Relevant Relay PR: getsentry/relay#5276

    Having strictTracePropagation turned on, means the SDK decides against continuing an incoming trace, since there is no baggage header which carries the orgId to compare against the local org id. In this case, the DSC is computed from the local SDK and thus it is "valid" again.

    My question is: Do you know why your service is receiving requests with a sentry-trace but not a baggage header? The case we foresaw this happening in is when old SDKs (by now really old SDKs) that didn't implement our baggage propagation mechanism would send. Curious if this is the case for you or if there's another reason.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions