Is there an existing issue for this?
How do you use Sentry?
Self-hosted/on-premise
Which SDK are you using?
@sentry/nestjs
SDK Version
10.66.0
Framework Version
NestJS 11
Link to Sentry event
No response
Reproduction Example/SDK Setup
SDK ≥10.64
Sentry.init({ debug: false, dsn: config.SENTRY_DSN, enabled: isPreproductionOrProduction(), enableMetrics: false, tracesSampleRate: 0, environment: config.NODE_ENV, release: version, })
Steps to Reproduce
- Nest app with
@sentry/nestjs (SDK ≥10.64) + SentryModule.forRoot(), Sentry.init({ tracesSampleRate: 0 })
- Run under sustained HTTP / messaging load for ≥1h
- Force GC and compare
old_space / take heap snapshots
Expected Result
- An ended / frozen span should not keep accumulating children, or
_sentryChildSpans should be cleared / bounded when a span ends, or
- Bootstrap /
app_creation spans should not remain active parents after Nest boot completes
Actual Result
Ended Create Nest App span retains an unbounded _sentryChildSpans set for process lifetime → continuous Scope / SentryNonRecordingSpan growth even with tracesSampleRate: 0.
Additional Context
After upgrading from @sentry/nestjs@10.62.0, a long-lived Nest service’s JS heap grew ~80 MB/h under production traffic and never reclaimed on forced GC.
Heap snapshots ~ 70 minutes apart showed almost 1:1 growth of Scope and SentryNonRecordingSpan (~ +176k each). Retention was dominated by a single root span:
- name:
Create Nest App
- op / attrs:
app_creation.nestjs, sentry.origin=auto.http.otel.nestjs, nestjs.module=AppModule
_sentryChildSpans: ~154k direct children (of ~233k total child links in the heap)
- Span also carried
sentry.tracerProviderSpan (marker for spans from Sentry’s minimal tracer provider)
Important: this root span was already ended / frozen (_frozen: true, _status set). It still kept receiving children via addChildSpanToSpan into a strong Set (_sentryChildSpans), so memory grew for the lifetime of the process.
Observed constructor deltas (shallow, ~70 min):
| Constructor |
Δ count |
Δ shallow |
Object |
+1.09M |
+55 MB |
Scope |
+176k |
+31 MB |
Array |
+709k |
+22 MB |
SentryNonRecordingSpan |
+176k |
+19 MB |
WeakRef |
+528k |
+16 MB |
Dominant retainer shape:
Scope ←_sentryScope— SentryNonRecordingSpan ←(Set)— _sentryChildSpans ← SentrySpan("Create Nest App")
Same growth pattern appeared on a second service after the same SDK bump, at a lower rate matching lower traffic — points away from app-specific code.
Suspected regression
10.64.0 introduced default use of Sentry’s minimal SentryTracerProvider instead of the full OTel BasicTracerProvider:
The leaking root is created by Nest instrumentation (getAppCreationSpanOptions / orchestrion NESTJS_APP_CREATION → startInactiveSpan). Children are attached via addChildSpanToSpan in @sentry/core with no guard for an already-ended parent and no upper bound on _sentryChildSpans.
Related prior discussion (different trigger, same “never-pruned root span” failure mode): #13412
Upstream WeakRef work (#21242) is present in 10.66 but does not stop strong retention through _sentryChildSpans.
Workaround (confirmed on our prod)
Sentry.init({
// ...
openTelemetryBasicTracerProvider: true,
})
After deploying this, container memory on the affected Nest service returned to a flat profile.
The httpIntegration({ ignoreOutgoingRequests: () => true }) workaround from #13412 does not apply: the accumulator is the Nest app_creation span, not an outgoing HTTP/WebSocket span.
Note on the workaround's lifetime
openTelemetryBasicTracerProvider: true is currently our only mitigation. #22557 (merged to develop, not yet in a published release as of 10.69.0) removes this option along with SentrySpanProcessor / SentrySampler.
Once that lands, TypeScript will reject the option as unknown. Deleting it to silence the error silently reintroduces the leak, because initOtel no longer has an opt-out and the minimal SentryTracerProvider becomes the only path.
If the accumulation is inherent to SentryTracerProvider, removing the opt-out leaves affected Nest services with no mitigation short of disabling Sentry. Relevant to the direction in #22486.
Happy to provide redacted heap-snapshot excerpts / retainer dumps if needed.
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/nestjs
SDK Version
10.66.0
Framework Version
NestJS 11
Link to Sentry event
No response
Reproduction Example/SDK Setup
SDK ≥10.64
Sentry.init({ debug: false, dsn: config.SENTRY_DSN, enabled: isPreproductionOrProduction(), enableMetrics: false, tracesSampleRate: 0, environment: config.NODE_ENV, release: version, })Steps to Reproduce
@sentry/nestjs(SDK ≥10.64) +SentryModule.forRoot(),Sentry.init({ tracesSampleRate: 0 })old_space/ take heap snapshotsExpected Result
_sentryChildSpansshould be cleared / bounded when a span ends, orapp_creationspans should not remain active parents after Nest boot completesActual Result
Ended
Create Nest Appspan retains an unbounded_sentryChildSpansset for process lifetime → continuousScope/SentryNonRecordingSpangrowth even withtracesSampleRate: 0.Additional Context
After upgrading from
@sentry/nestjs@10.62.0, a long-lived Nest service’s JS heap grew ~80 MB/h under production traffic and never reclaimed on forced GC.Heap snapshots ~ 70 minutes apart showed almost 1:1 growth of
ScopeandSentryNonRecordingSpan(~ +176k each). Retention was dominated by a single root span:Create Nest Appapp_creation.nestjs,sentry.origin=auto.http.otel.nestjs,nestjs.module=AppModule_sentryChildSpans: ~154k direct children (of ~233k total child links in the heap)sentry.tracerProviderSpan(marker for spans from Sentry’s minimal tracer provider)Important: this root span was already ended / frozen (
_frozen: true,_statusset). It still kept receiving children viaaddChildSpanToSpaninto a strongSet(_sentryChildSpans), so memory grew for the lifetime of the process.Observed constructor deltas (shallow, ~70 min):
ObjectScopeArraySentryNonRecordingSpanWeakRefDominant retainer shape:
Same growth pattern appeared on a second service after the same SDK bump, at a lower rate matching lower traffic — points away from app-specific code.
Suspected regression
10.64.0 introduced default use of Sentry’s minimal
SentryTracerProviderinstead of the full OTelBasicTracerProvider:The leaking root is created by Nest instrumentation (
getAppCreationSpanOptions/ orchestrionNESTJS_APP_CREATION→startInactiveSpan). Children are attached viaaddChildSpanToSpanin@sentry/corewith no guard for an already-ended parent and no upper bound on_sentryChildSpans.Related prior discussion (different trigger, same “never-pruned root span” failure mode): #13412
Upstream WeakRef work (#21242) is present in 10.66 but does not stop strong retention through
_sentryChildSpans.Workaround (confirmed on our prod)
After deploying this, container memory on the affected Nest service returned to a flat profile.
The
httpIntegration({ ignoreOutgoingRequests: () => true })workaround from #13412 does not apply: the accumulator is the Nestapp_creationspan, not an outgoing HTTP/WebSocket span.Note on the workaround's lifetime
openTelemetryBasicTracerProvider: trueis currently our only mitigation. #22557 (merged todevelop, not yet in a published release as of 10.69.0) removes this option along withSentrySpanProcessor/SentrySampler.Once that lands, TypeScript will reject the option as unknown. Deleting it to silence the error silently reintroduces the leak, because
initOtelno longer has an opt-out and the minimalSentryTracerProviderbecomes the only path.If the accumulation is inherent to
SentryTracerProvider, removing the opt-out leaves affected Nest services with no mitigation short of disabling Sentry. Relevant to the direction in #22486.Happy to provide redacted heap-snapshot excerpts / retainer dumps if needed.
Priority
React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding
+1orme too, to help us triage it.