Is there an existing issue for this?
How do you use Sentry?
Self-hosted/on-premise
Which SDK are you using?
@sentry/nuxt
SDK Version
10.69.0
Framework Version
Nuxt 4.5.1
Link to Sentry event
No response
Reproduction Example/SDK Setup
Minimal Nuxt 4 app, @sentry/nuxt module enabled, one page + one API route.
A plain node:http server on :7877 acts as the tunnel and counts envelopes by type.
// sentry.server.config.ts
Sentry.init({
dsn: 'https://<key>@example.net/4',
environment: 'demo',
release: 'demo-1',
enableLogs: true,
tunnel: 'http://localhost:7877/sentry', // local sink that counts envelopes
tracesSampleRate: 0.001,
debug: false,
})
Built with the node-server preset and started with
node --import ./.output/server/sentry.server.config.mjs .output/server/index.mjs.
Steps to Reproduce
- Build the app above and start it with the --import flag.
- Point
tunnel at a local HTTP server that logs the type of each envelope
(parse the second line of the envelope body) and answers 200.
- Issue 30 plain GET requests to the page route, one after another.
- Wait ~5s and count the envelopes received by the sink.
Expected Result
Client reports are aggregated and flushed on an interval
(clientReportFlushInterval, 60s by default), so 30 requests should produce
roughly one client_report envelope, not one per request.
Actual Result
30 requests produce 60 envelopes — one client_report and one sessions per request:
SSR page requests: 30
envelopes total: 60
by type: {"client_report":30,"sessions":30}
client_report per page: 1.00
client_report sizes: 212..214 B
The cause is in packages/nuxt/src/server/sdk.ts:
httpIntegration({
instrumentation: {
responseHook: () => {
vercelWaitUntil(flushSafelyWithTimeout()) // argument is evaluated unconditionally
},
},
})
vercelWaitUntil is a no-op outside Vercel, but flushSafelyWithTimeout() is
called before it, so flush(2000) runs on every HTTP response on every
platform. flush() calls _flushOutcomes(), so any pending outcome is
immediately shipped as its own envelope instead of being aggregated.
Additional Context
Since 10.65.0, outgoing requests without a parent span record a no_parent_span
outcome (#22113, #20350). Combined with the per-response flush, each outcome
ships as its own envelope instead of being batched. Bisected on a
background-only workload (zero inbound requests, 100 outbound calls made
outside any request transaction):
| @sentry/nuxt |
envelopes |
| 10.52 – 10.64 |
0 |
| 10.65.0+ |
100 (no_parent_span:span ×100) |
10.69.0 + sendClientReports: false |
0 |
Worst case: if another OpenTelemetry setup calls NodeTracerProvider.register()
after Sentry.init() (our own misconfiguration, fixed separately), the
SentryContextManager is replaced and no request ever has a parent span. The
SDK logs four errors about the missing setup, but keeps recording outcomes — so
every outbound request produces one, including the transport's own envelope
delivery. Each envelope response triggers the next flush, so the rate is bound
only by ingest RTT: 1166 envelopes in 90s from an idle pod (~500/s in-cluster at
RTT 2ms, ~13/s locally at RTT 60ms), with zero user traffic.
Suggestions:
- Call
flushSafelyWithTimeout() lazily, inside the branch where waitUntil
semantics apply, so non-Vercel deployments don't flush per response.
- Skip
no_parent_span outcomes once the SDK has detected its context manager
was replaced — they carry no signal in that state.
Priority
React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it.
Is there an existing issue for this?
How do you use Sentry?
Self-hosted/on-premise
Which SDK are you using?
@sentry/nuxt
SDK Version
10.69.0
Framework Version
Nuxt 4.5.1
Link to Sentry event
No response
Reproduction Example/SDK Setup
Minimal Nuxt 4 app,
@sentry/nuxtmodule enabled, one page + one API route.A plain
node:httpserver on :7877 acts as the tunnel and counts envelopes by type.Built with the
node-serverpreset and started withnode --import ./.output/server/sentry.server.config.mjs .output/server/index.mjs.Steps to Reproduce
tunnelat a local HTTP server that logs thetypeof each envelope(parse the second line of the envelope body) and answers 200.
Expected Result
Client reports are aggregated and flushed on an interval
(
clientReportFlushInterval, 60s by default), so 30 requests should produceroughly one
client_reportenvelope, not one per request.Actual Result
30 requests produce 60 envelopes — one
client_reportand onesessionsper request:The cause is in
packages/nuxt/src/server/sdk.ts:vercelWaitUntilis a no-op outside Vercel, butflushSafelyWithTimeout()iscalled before it, so
flush(2000)runs on every HTTP response on everyplatform.
flush()calls_flushOutcomes(), so any pending outcome isimmediately shipped as its own envelope instead of being aggregated.
Additional Context
Since 10.65.0, outgoing requests without a parent span record a
no_parent_spanoutcome (#22113, #20350). Combined with the per-response flush, each outcome
ships as its own envelope instead of being batched. Bisected on a
background-only workload (zero inbound requests, 100 outbound calls made
outside any request transaction):
no_parent_span:span×100)sendClientReports: falseWorst case: if another OpenTelemetry setup calls
NodeTracerProvider.register()after
Sentry.init()(our own misconfiguration, fixed separately), theSentryContextManageris replaced and no request ever has a parent span. TheSDK logs four errors about the missing setup, but keeps recording outcomes — so
every outbound request produces one, including the transport's own envelope
delivery. Each envelope response triggers the next flush, so the rate is bound
only by ingest RTT: 1166 envelopes in 90s from an idle pod (~500/s in-cluster at
RTT 2ms, ~13/s locally at RTT 60ms), with zero user traffic.
Suggestions:
flushSafelyWithTimeout()lazily, inside the branch wherewaitUntilsemantics apply, so non-Vercel deployments don't flush per response.
no_parent_spanoutcomes once the SDK has detected its context managerwas replaced — they carry no signal in that state.
Priority
React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding
+1orme too, to help us triage it.