Repository navigation
Node profile frames never symbolicated with Turbopack native debug IDs, because the profiler gates resource collection on _sentryDebugIds #24202
Description
Is there an existing issue for this?
- I have checked for existing issues https://github.com/getsentry/sentry-javascript/issues
- I have reviewed the documentation https://docs.sentry.io/
- I am using the latest SDK release https://github.com/getsentry/sentry-javascript/releases
How do you use Sentry?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/nextjs
SDK Version
10.73.0
Framework Version
Next.js 16.3.0 (Turbopack)
Link to Sentry event
No response
Reproduction Example/SDK Setup
next.config.ts:
import { withSentryConfig } from '@sentry/nextjs';
export default withSentryConfig(
{
output: 'standalone',
// Turbopack's native debug ID generation
turbopack: { debugIds: true },
serverExternalPackages: ['@sentry/profiling-node'],
},
{
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
release: { name: process.env.SENTRY_RELEASE },
widenClientFileUpload: true,
sourcemaps: { deleteSourcemapsAfterUpload: false },
},
);instrumentation.ts:
import * as Sentry from '@sentry/nextjs';
import { nodeProfilingIntegration } from '@sentry/profiling-node';
Sentry.init({
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 1.0,
profileSessionSampleRate: 1.0,
profileLifecycle: 'trace',
integrations: [nodeProfilingIntegration()],
});Steps to Reproduce
- Build a Next.js 16 app with Turbopack,
turbopack: { debugIds: true }andoutput: 'standalone', using@sentry/nextjsand@sentry/profiling-node10.73.0 with server-side continuous profiling. - Build for production and deploy the standalone output. Source map upload runs through
runAfterProductionCompileand the emitted server chunks carry both a//# debugId=comment and the native registration snippet that writesglobalThis._debugIds. - Send traffic so the same process produces both an error event and a profile chunk.
- Compare
debug_metaon the error event withdebug_metaon the profile chunk, and open the flamegraph.
Expected Result
The profile chunk should carry a populated debug_meta.images, the same way the error event does, so the flamegraph resolves to original file paths and function names.
Actual Result
The error event symbolicates correctly. Its debug_meta contains the right image for the server chunk:
{
"images": [
{
"code_file": "app:///_next/server/chunks/[root-of-the-server]__aaaaaaa._.js",
"debug_id": "00000000-0000-0000-0000-000000000000",
"type": "sourcemap"
}
]
}The profile chunk from the same process, same release and same server chunk arrives with:
"debug_meta": {}Flamegraph frames stay as minified identifiers (oV, aN, eQ) with raw chunk paths such as /srv/app/.next/server/chunks/[root-of-the-server]__aaaaaaa._.js:11:64839. Because debug_meta is empty, the backend skips profile symbolication entirely rather than failing at it.
Additional Context
With turbopack.debugIds enabled, @sentry/nextjs treats debug IDs as natively provided and skips injectDebugIds in handleRunAfterProductionCompile.ts. Nothing writes globalThis._sentryDebugIds in that case, only Turbopack's globalThis._debugIds. Error events are unaffected because getFilenameToDebugIdMap in packages/core/src/utils/debug-ids.ts reads both globals.
The profiler doesn't. @sentry/node-cpu-profiler 2.4.3 decides whether to collect script resources from the legacy global alone:
// lib/index.js
return PrivateCpuProfilerBindings.stopProfiling(name, format, threadId, !!global._sentryDebugIds);That argument is collect_resources in the binding, and TranslateProfile substitutes an empty array for resources when it is false. createProfileChunkPayload derives the images from exactly that array via applyDebugMetadata(client, cpuProfile.resources), so the chunk leaves the process with nothing to symbolicate.
Resolution itself is fine. Taking the registration snippet out of a real server chunk and passing that chunk's own path through getDebugImagesForResources returns the correct image, so the gate looks like the only broken part.
Setting globalThis._sentryDebugIds = globalThis._sentryDebugIds || {} before Sentry.init works around it, since the empty object passes the check and the merged map still resolves the native IDs.
Worth noting separately that frames from ESM packages under node_modules arrive as file:///... paths, which the backend rejects per getsentry/sentry#109520. Turbopack's own server chunks use plain absolute paths and aren't affected by that one.
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.
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsNo status
Activity
linear-code commentedon Sep 8, 2026
JS-3624
chargome commentedon Sep 8, 2026
Nice catch, fixing right away @InterstellarStella
fix: Collect script resources when only _debugIds is set (#40)
fix(profiling-node): Bump @sentry/node-cpu-profiler to 2.4.4 (#24229)