From ee764f88d514e8d33ae9e810bef8abdd779a9e86 Mon Sep 17 00:00:00 2001 From: Sergiy Dybskiy Date: Thu, 4 Jun 2026 15:17:14 -0400 Subject: [PATCH 1/3] fix(node-core): Read __SENTRY_SERVER_MODULES__ lazily so Turbopack injection is honored MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `modulesIntegration` captured `__SENTRY_SERVER_MODULES__` into a module-level `const` at evaluation time. That works for webpack (DefinePlugin replaces the token with a literal at build time) but not for Turbopack: the value-injection loader assigns `globalThis.__SENTRY_SERVER_MODULES__` at runtime, and the instrumentation file's ESM imports are hoisted above that assignment — so this module evaluates before the global is set and the capture is always empty. As a result, on Next.js 16 / Turbopack production builds (e.g. Vercel) every module-detection-based auto integration silently never activates (vercelAI, openAI, anthropic, googleGenAI, langChain, langGraph), and `event.modules` is missing server dependencies. Read the value lazily instead: prefer the build-time-replaced token (webpack), then fall back to `GLOBAL_OBJ.__SENTRY_SERVER_MODULES__` (Turbopack). Adds a regression test that fails when the value is captured at module-eval time. Ref: https://github.com/getsentry/sentry-javascript/issues/19147 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../node-core/src/integrations/modules.ts | 29 ++++++++++++--- .../test/integrations/modules.test.ts | 36 +++++++++++++++++++ 2 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 packages/node-core/test/integrations/modules.test.ts diff --git a/packages/node-core/src/integrations/modules.ts b/packages/node-core/src/integrations/modules.ts index 7079f4a2fab8..6fe88699780d 100644 --- a/packages/node-core/src/integrations/modules.ts +++ b/packages/node-core/src/integrations/modules.ts @@ -1,6 +1,6 @@ import { existsSync, readFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; -import type { IntegrationFn } from '@sentry/core'; +import { GLOBAL_OBJ, type IntegrationFn } from '@sentry/core'; import { isCjs } from '../utils/detection'; type ModuleInfo = Record; @@ -12,10 +12,29 @@ const INTEGRATION_NAME = 'Modules'; declare const __SENTRY_SERVER_MODULES__: Record; /** - * `__SENTRY_SERVER_MODULES__` can be replaced at build time with the modules loaded by the server. - * Right now, we leverage this in Next.js to circumvent the problem that we do not get access to these things at runtime. + * Reads the modules that were injected at build time into `__SENTRY_SERVER_MODULES__` + * (e.g. by the Next.js SDK, to work around not having access to these at runtime). + * + * This MUST be read lazily (on every call) rather than captured once at module-evaluation + * time, because the two supported bundlers inject the value differently: + * - webpack replaces the `__SENTRY_SERVER_MODULES__` token with a literal via `DefinePlugin` + * (available as soon as this module is evaluated). + * - Turbopack assigns `globalThis.__SENTRY_SERVER_MODULES__` at runtime, from a value-injection + * loader applied to `instrumentation.*`. The instrumentation file's ESM imports are hoisted + * above that assignment, so this module is evaluated *before* the global is set. A + * module-level `const` capture would therefore always be empty under Turbopack, silently + * disabling every module-detection-based auto integration (Vercel AI, OpenAI, Anthropic, + * Google GenAI, LangChain, LangGraph). See getsentry/sentry-javascript#19147. */ -const SERVER_MODULES = typeof __SENTRY_SERVER_MODULES__ === 'undefined' ? {} : __SENTRY_SERVER_MODULES__; +function getServerModules(): Record { + // webpack: the token is replaced with a literal at build time. + if (typeof __SENTRY_SERVER_MODULES__ !== 'undefined') { + return __SENTRY_SERVER_MODULES__; + } + // Turbopack: the value is assigned onto the global object at runtime. + return (GLOBAL_OBJ as typeof GLOBAL_OBJ & { __SENTRY_SERVER_MODULES__?: Record }) + .__SENTRY_SERVER_MODULES__ ?? {}; +} const _modulesIntegration = (() => { return { @@ -52,7 +71,7 @@ function getRequireCachePaths(): string[] { /** Extract information about package.json modules */ function collectModules(): ModuleInfo { return { - ...SERVER_MODULES, + ...getServerModules(), ...getModulesFromPackageJson(), ...(isCjs() ? collectRequireModules() : {}), }; diff --git a/packages/node-core/test/integrations/modules.test.ts b/packages/node-core/test/integrations/modules.test.ts new file mode 100644 index 000000000000..31ee5671e40a --- /dev/null +++ b/packages/node-core/test/integrations/modules.test.ts @@ -0,0 +1,36 @@ +import { GLOBAL_OBJ } from '@sentry/core'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +type GlobalWithModules = typeof GLOBAL_OBJ & { __SENTRY_SERVER_MODULES__?: Record }; + +describe('modulesIntegration', () => { + afterEach(() => { + delete (GLOBAL_OBJ as GlobalWithModules).__SENTRY_SERVER_MODULES__; + vi.resetModules(); + }); + + it('includes modules injected onto the global AFTER this module was evaluated (Turbopack ordering)', async () => { + // Re-evaluate the integration module with no injected global present. This mirrors Turbopack: + // the instrumentation file's hoisted ESM imports evaluate this module *before* its + // `globalThis.__SENTRY_SERVER_MODULES__ = {...}` assignment runs. A module-level capture would + // freeze an empty value here and never see the injection (getsentry/sentry-javascript#19147). + vi.resetModules(); + const { modulesIntegration } = await import('../../src/integrations/modules'); + + // The runtime injection happens only now — after the module is already evaluated. + (GLOBAL_OBJ as GlobalWithModules).__SENTRY_SERVER_MODULES__ = { '@sentry/turbopack-injected': '1.2.3' }; + + const modules = modulesIntegration().getModules?.() ?? {}; + expect(modules['@sentry/turbopack-injected']).toBe('1.2.3'); + }); + + it('reads modules already present before evaluation (webpack DefinePlugin / pre-set global)', async () => { + (GLOBAL_OBJ as GlobalWithModules).__SENTRY_SERVER_MODULES__ = { '@sentry/prebuilt-injected': '4.5.6' }; + + vi.resetModules(); + const { modulesIntegration } = await import('../../src/integrations/modules'); + + const modules = modulesIntegration().getModules?.() ?? {}; + expect(modules['@sentry/prebuilt-injected']).toBe('4.5.6'); + }); +}); From 48dbf6f5c8864c8af40a47ca8e505f835b3cecc9 Mon Sep 17 00:00:00 2001 From: Sergiy Dybskiy Date: Thu, 4 Jun 2026 15:18:22 -0400 Subject: [PATCH 2/3] docs(changelog): add entry for #21339 Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cd0e2bcab92..7d0b5ac8d8da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +- **fix(node-core): Read `__SENTRY_SERVER_MODULES__` lazily so Turbopack injection is honored [#21339](https://github.com/getsentry/sentry-javascript/pull/21339)** + + On Next.js 16 / Turbopack production builds, `modulesIntegration` captured `__SENTRY_SERVER_MODULES__` at module-evaluation time, before Turbopack's runtime `globalThis` assignment ran. This silently disabled module-detection-based auto integrations (Vercel AI, OpenAI, Anthropic, Google GenAI, LangChain, LangGraph) and left `event.modules` empty. The value is now read lazily, supporting both the webpack (`DefinePlugin`) and Turbopack (runtime global) injection styles. + - **ref(core): Deprecate `sendDefaultPii` in favor of `dataCollection` [#21277](https://github.com/getsentry/sentry-javascript/pull/21277)** `sendDefaultPii` is deprecated and will be removed in v11. The new `dataCollection` option lets you control each category of collected data. From 7682515f8a5e2ce3a54258a7258defbbd0032bde Mon Sep 17 00:00:00 2001 From: Sergiy Dybskiy Date: Thu, 4 Jun 2026 17:13:37 -0400 Subject: [PATCH 3/3] style(node-core): Apply oxfmt formatting to modules.ts Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/node-core/src/integrations/modules.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/node-core/src/integrations/modules.ts b/packages/node-core/src/integrations/modules.ts index 6fe88699780d..a7b4ad87768e 100644 --- a/packages/node-core/src/integrations/modules.ts +++ b/packages/node-core/src/integrations/modules.ts @@ -32,8 +32,10 @@ function getServerModules(): Record { return __SENTRY_SERVER_MODULES__; } // Turbopack: the value is assigned onto the global object at runtime. - return (GLOBAL_OBJ as typeof GLOBAL_OBJ & { __SENTRY_SERVER_MODULES__?: Record }) - .__SENTRY_SERVER_MODULES__ ?? {}; + return ( + (GLOBAL_OBJ as typeof GLOBAL_OBJ & { __SENTRY_SERVER_MODULES__?: Record }) + .__SENTRY_SERVER_MODULES__ ?? {} + ); } const _modulesIntegration = (() => {