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
@sentry/profiling-node 10.62.0 (also reproduced on develop as of 2026-08-01)
Framework Version
No response
Link to Sentry event
No response
Reproduction Example/SDK Setup
const Sentry = require("@sentry/node");
const { nodeProfilingIntegration } = require("@sentry/profiling-node");
Sentry.init({
dsn: "<dsn>",
integrations: [nodeProfilingIntegration()],
tracesSampleRate: 1.0,
profileSessionSampleRate: 0, // session NOT sampled -> expect NO profiling
profileLifecycle: "trace",
});
// Start any span. A profiling chunk is still started even though
// profileSessionSampleRate is 0.
Sentry.startSpan({ name: "test" }, () => {
// CpuProfilerBindings.startProfiling(...) runs here
});
Steps to Reproduce
- Initialize the SDK with
profileLifecycle: 'trace' and profileSessionSampleRate: 0 (see SDK setup above).
- Start any span, e.g.
Sentry.startSpan({ name: "test" }, () => {}).
- Observe that chunk profiling starts anyway — with debug enabled, the SDK logs
[Profiling] Profiling mode is trace. and CpuProfilerBindings.startProfiling(...) is invoked on span start, even though the session sampling decision is false.
Expected Result
With profileSessionSampleRate: 0, the session is not sampled (_sampled === false), so no profiling should occur in any lifecycle mode.
Actual Result
In trace mode, chunk profiling starts on spanStart regardless of the session
sampling decision. With profileSessionSampleRate: 0, this._sampled is computed
as false at init, but the trace lifecycle path never consults it —
CpuProfilerBindings.startProfiling(...) runs as soon as the active-span counter
goes from 0 to 1.
With debug enabled, the SDK logs [Profiling] Profiling mode is trace. and then
starts a profiling chunk. There is no "not sampled" log, because the only place
that emits it (_startProfiler()) is the manual-mode entry point, which
early-returns for trace mode anyway.
Verified in 10.10.0, 10.62.0, and current develop (as of 2026-08-01) — the
behavior is identical.
Additional Context
Root cause (source walkthrough)
In packages/profiling-node/src/integration.ts (develop):
initialize() computes the session sampling decision:
this._sampled = this._sessionSamplingRate < (options.profileSessionSampleRate ?? 0)
- For
current mode with _profileLifecycle === 'trace', setup calls
this._startTraceLifecycleProfiling() unconditionally — no _sampled check.
_startTraceLifecycleProfiling() registers a spanStart handler that calls
this._startChunkProfiling() directly when the active-span counter goes 0 → 1.
- Neither
_startTraceLifecycleProfiling() nor _startChunkProfiling() checks
this._sampled.
this._sampled is only checked inside _startProfiler(), the manual-mode
entry point, which early-returns for trace mode.
So the session sampling gate is effectively manual-only.
Comparison with the browser SDK
The browser UI profiling implementation does gate trace-lifecycle profiling on
the session sampling decision. In packages/browser/src/profiling/UIProfiler.ts,
initialize() resolves _sessionSampled via shouldProfileSession(), and the
spanStart listener registered by _setupTraceLifecycleListeners() returns
early when the session is not sampled. The Node implementation registers the
equivalent listener without any such check, so the two platforms behave
differently for the same option.
Docs
The Node profiling docs describe profileSessionSampleRate as a session-level
gate evaluated once at SDK init, useful when you "would only like a subset of
those services to be profiled". There is no note limiting it to the manual
lifecycle, and the trace-lifecycle setup example includes the option.
Impact
Users setting a low profileSessionSampleRate with profileLifecycle: 'trace'
to limit profiling overhead and continuous-profile-hours across a fleet will
instead profile on every process that has active spans, producing more overhead
and billed profile hours than intended.
Question
Is this intended? If trace mode is designed to ignore profileSessionSampleRate,
the docs should note that the session-level gate applies only to the manual
lifecycle. If not, _startTraceLifecycleProfiling() (or its setup) should
short-circuit when !this._sampled, matching the browser implementation.
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?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/node
SDK Version
@sentry/profiling-node 10.62.0 (also reproduced on develop as of 2026-08-01)
Framework Version
No response
Link to Sentry event
No response
Reproduction Example/SDK Setup
Steps to Reproduce
profileLifecycle: 'trace'andprofileSessionSampleRate: 0(see SDK setup above).Sentry.startSpan({ name: "test" }, () => {}).[Profiling] Profiling mode is trace.andCpuProfilerBindings.startProfiling(...)is invoked on span start, even though the session sampling decision isfalse.Expected Result
With profileSessionSampleRate: 0, the session is not sampled (_sampled === false), so no profiling should occur in any lifecycle mode.
Actual Result
In
tracemode, chunk profiling starts onspanStartregardless of the sessionsampling decision. With
profileSessionSampleRate: 0,this._sampledis computedas
falseat init, but the trace lifecycle path never consults it —CpuProfilerBindings.startProfiling(...)runs as soon as the active-span countergoes from 0 to 1.
With debug enabled, the SDK logs
[Profiling] Profiling mode is trace.and thenstarts a profiling chunk. There is no "not sampled" log, because the only place
that emits it (
_startProfiler()) is the manual-mode entry point, whichearly-returns for trace mode anyway.
Verified in 10.10.0, 10.62.0, and current
develop(as of 2026-08-01) — thebehavior is identical.
Additional Context
Root cause (source walkthrough)
In
packages/profiling-node/src/integration.ts(develop):initialize()computes the session sampling decision:this._sampled = this._sessionSamplingRate < (options.profileSessionSampleRate ?? 0)currentmode with_profileLifecycle === 'trace', setup callsthis._startTraceLifecycleProfiling()unconditionally — no_sampledcheck._startTraceLifecycleProfiling()registers aspanStarthandler that callsthis._startChunkProfiling()directly when the active-span counter goes 0 → 1._startTraceLifecycleProfiling()nor_startChunkProfiling()checksthis._sampled.this._sampledis only checked inside_startProfiler(), the manual-modeentry point, which early-returns for trace mode.
So the session sampling gate is effectively manual-only.
Comparison with the browser SDK
The browser UI profiling implementation does gate trace-lifecycle profiling on
the session sampling decision. In
packages/browser/src/profiling/UIProfiler.ts,initialize()resolves_sessionSampledviashouldProfileSession(), and thespanStartlistener registered by_setupTraceLifecycleListeners()returnsearly when the session is not sampled. The Node implementation registers the
equivalent listener without any such check, so the two platforms behave
differently for the same option.
Docs
The Node profiling docs describe
profileSessionSampleRateas a session-levelgate evaluated once at SDK init, useful when you "would only like a subset of
those services to be profiled". There is no note limiting it to the
manuallifecycle, and the trace-lifecycle setup example includes the option.
Impact
Users setting a low
profileSessionSampleRatewithprofileLifecycle: 'trace'to limit profiling overhead and continuous-profile-hours across a fleet will
instead profile on every process that has active spans, producing more overhead
and billed profile hours than intended.
Question
Is this intended? If trace mode is designed to ignore
profileSessionSampleRate,the docs should note that the session-level gate applies only to the
manuallifecycle. If not,
_startTraceLifecycleProfiling()(or its setup) shouldshort-circuit when
!this._sampled, matching the browser implementation.Priority
React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding
+1orme too, to help us triage it.