From 61d9e32bb333a79fa7bd273a2ed0066996d2ceb2 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 21 Aug 2026 11:38:47 +0200 Subject: [PATCH 1/4] feat(node): Add build-time opt-out for runtime channel injection Introduce a `__SENTRY_CHANNEL_INJECTION__` treeshaking flag (mirroring `__SENTRY_TRACING__`) that removes the runtime diagnostics-channel injection when text-replaced with `false`, and expose it through the bundler plugins' `bundleSizeOptimizations.excludeChannelInjection`. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/core/build-plugin-manager.ts | 3 +++ .../bundler-plugins/src/core/options-mapping.ts | 1 + packages/bundler-plugins/src/core/types.ts | 16 ++++++++++++++++ packages/node/src/sdk/index.ts | 15 +++++++++++---- .../test/sdk/diagnosticsChannelInjection.test.ts | 14 ++++++++++++++ 5 files changed, 45 insertions(+), 4 deletions(-) diff --git a/packages/bundler-plugins/src/core/build-plugin-manager.ts b/packages/bundler-plugins/src/core/build-plugin-manager.ts index 1e2d2b6bad50..b44c8203b242 100644 --- a/packages/bundler-plugins/src/core/build-plugin-manager.ts +++ b/packages/bundler-plugins/src/core/build-plugin-manager.ts @@ -335,6 +335,9 @@ export function createSentryBuildPluginManager( if (bundleSizeOptimizations.excludeTracing) { bundleSizeOptimizationReplacementValues['__SENTRY_TRACING__'] = false; } + if (bundleSizeOptimizations.excludeChannelInjection) { + bundleSizeOptimizationReplacementValues['__SENTRY_CHANNEL_INJECTION__'] = false; + } if (bundleSizeOptimizations.excludeReplayCanvas) { bundleSizeOptimizationReplacementValues['__RRWEB_EXCLUDE_CANVAS__'] = true; } diff --git a/packages/bundler-plugins/src/core/options-mapping.ts b/packages/bundler-plugins/src/core/options-mapping.ts index df716b922851..42629509a737 100644 --- a/packages/bundler-plugins/src/core/options-mapping.ts +++ b/packages/bundler-plugins/src/core/options-mapping.ts @@ -60,6 +60,7 @@ export type NormalizedOptions = { | { excludeDebugStatements?: boolean; excludeTracing?: boolean; + excludeChannelInjection?: boolean; excludeReplayCanvas?: boolean; excludeReplayShadowDom?: boolean; excludeReplayIframe?: boolean; diff --git a/packages/bundler-plugins/src/core/types.ts b/packages/bundler-plugins/src/core/types.ts index 7542b3c708b6..405687596ec1 100644 --- a/packages/bundler-plugins/src/core/types.ts +++ b/packages/bundler-plugins/src/core/types.ts @@ -303,6 +303,21 @@ export interface Options { */ excludeTracing?: boolean; + /** + * Exclude the Node SDK's runtime diagnostics-channel injection from the bundle. + * + * If set to `true`, the plugin will attempt to tree-shake (remove) code that installs the Node SDK's + * runtime module hooks (e.g. for Express instrumentation) at load time. Note that the success of this + * depends on tree-shaking being enabled in your build tooling. + * + * Only enable this when the diagnostics channels are injected at build time (via the bundler plugin) or + * when you otherwise do not rely on the runtime channel injection. This is equivalent to setting + * `enableRuntimeChannelInjection: false` in the SDK's `init` options. + * + * @default false + */ + excludeChannelInjection?: boolean; + /** * If set to `true`, the plugin will attempt to tree-shake (remove) code related to the Sentry SDK's Session Replay Canvas recording functionality. * Note that the success of this depends on tree-shaking being enabled in your build tooling. @@ -526,6 +541,7 @@ export type IncludeEntry = { export interface SentrySDKBuildFlags extends Record { __SENTRY_DEBUG__?: boolean; __SENTRY_TRACING__?: boolean; + __SENTRY_CHANNEL_INJECTION__?: boolean; __RRWEB_EXCLUDE_CANVAS__?: boolean; __RRWEB_EXCLUDE_IFRAME__?: boolean; __RRWEB_EXCLUDE_SHADOW_DOM__?: boolean; diff --git a/packages/node/src/sdk/index.ts b/packages/node/src/sdk/index.ts index b4249a0ec89a..38b7d46355f3 100644 --- a/packages/node/src/sdk/index.ts +++ b/packages/node/src/sdk/index.ts @@ -41,6 +41,10 @@ import { defaultStackParser, getSentryRelease } from './api'; import { NodeClient } from './client'; import { initOpenTelemetry } from './initOtel'; +// Treeshakable guard to remove all code related to runtime diagnostics-channel injection. Set to +// `false` at build time by the Sentry bundler plugins' `bundleSizeOptimizations.excludeChannelInjection`. +declare const __SENTRY_CHANNEL_INJECTION__: boolean | undefined; + /** * Get the base default integrations shared by all Node SDK default-integration sets. */ @@ -155,10 +159,13 @@ function _init( }; // Install the channel-based (orchestrion diagnostics-channel) instrumentation hooks by default, - // independent of tracing — the channel integrations also capture errors, not just spans. Opt out - // with `enableRuntimeChannelInjection: false`. Install as early as possible, before the app imports - // its instrumented modules. - const useChannelInjection = options.enableRuntimeChannelInjection !== false; + // independent of tracing — the channel integrations also capture errors, not just spans. Opt out at + // runtime with `enableRuntimeChannelInjection: false`, or at build time via the bundler plugins' + // `bundleSizeOptimizations.excludeChannelInjection` (which tree-shakes this whole block away). + // Install as early as possible, before the app imports its instrumented modules. + const useChannelInjection = + (typeof __SENTRY_CHANNEL_INJECTION__ === 'undefined' || __SENTRY_CHANNEL_INJECTION__) && + options.enableRuntimeChannelInjection !== false; if (useChannelInjection) { registerDiagnosticsChannelInjection(); } diff --git a/packages/node/test/sdk/diagnosticsChannelInjection.test.ts b/packages/node/test/sdk/diagnosticsChannelInjection.test.ts index 5132f8cf6544..782b7a1c093d 100644 --- a/packages/node/test/sdk/diagnosticsChannelInjection.test.ts +++ b/packages/node/test/sdk/diagnosticsChannelInjection.test.ts @@ -68,4 +68,18 @@ describe('diagnostics-channel injection', () => { expect(registerDiagnosticsChannelInjection).toHaveBeenCalledTimes(1); expect(detectOrchestrionSetup).toHaveBeenCalledTimes(1); }); + + it('does not register the injection hooks but still runs detection when the `__SENTRY_CHANNEL_INJECTION__` build flag is false', () => { + // Simulates the bundler plugins' `bundleSizeOptimizations.excludeChannelInjection` text-replacing the flag. + vi.stubGlobal('__SENTRY_CHANNEL_INJECTION__', false); + + try { + init({ dsn: PUBLIC_DSN, tracesSampleRate: 1, enableOpenTelemetrySetup: false }); + + expect(registerDiagnosticsChannelInjection).not.toHaveBeenCalled(); + expect(detectOrchestrionSetup).toHaveBeenCalledTimes(1); + } finally { + vi.unstubAllGlobals(); + } + }); }); From 70601d5dcd23af030ec0e4a10160d3f7f2c388ec Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 26 Aug 2026 08:43:59 +0200 Subject: [PATCH 2/4] fixes and size limit --- .size-limit.js | 19 +++++++++++++++++++ .../buildTimeOptionsBase.ts | 15 +++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/.size-limit.js b/.size-limit.js index 9e07ab27adb6..dfdbd49bfbe9 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -447,6 +447,25 @@ module.exports = [ return config; }, }, + { + name: '@sentry/node - without channel injection', + path: 'packages/node/build/esm/index.js', + import: createImport('init'), + gzip: true, + limit: '104 KB', + disablePlugins: ['@size-limit/esbuild'], + ignore: [...builtinModules, ...nodePrefixedBuiltinModules], + modifyWebpackConfig: function (config) { + const webpack = require('webpack'); + config.plugins.push( + new webpack.DefinePlugin({ + __SENTRY_CHANNEL_INJECTION__: false, + }), + ); + config.optimization.minimize = true; + return config; + }, + }, // AWS SDK (ESM) { name: '@sentry/aws-serverless', diff --git a/packages/core/src/build-time-plugins/buildTimeOptionsBase.ts b/packages/core/src/build-time-plugins/buildTimeOptionsBase.ts index 0ed635105391..36a8967c8105 100644 --- a/packages/core/src/build-time-plugins/buildTimeOptionsBase.ts +++ b/packages/core/src/build-time-plugins/buildTimeOptionsBase.ts @@ -496,4 +496,19 @@ interface BundleSizeOptimizationsOptions { * @default false */ excludeReplayWorker?: boolean; + + /** + * Exclude the Node SDK's runtime diagnostics-channel injection from the bundle. + * + * If set to `true`, the plugin will attempt to tree-shake (remove) code that installs the Node SDK's + * runtime module hooks (e.g. for Express instrumentation) at load time. Note that the success of this + * depends on tree-shaking being enabled in your build tooling. + * + * Only enable this when the diagnostics channels are injected at build time (via the bundler plugin) or + * when you otherwise do not rely on the runtime channel injection. This is equivalent to setting + * `enableRuntimeChannelInjection: false` in the SDK's `init` options. + * + * @default false + */ + excludeChannelInjection?: boolean; } From 9e5a62d4adfbd2bc66e00ba2c8f044aaca7c4e0c Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Wed, 26 Aug 2026 08:44:04 +0200 Subject: [PATCH 3/4] Update packages/node/src/sdk/index.ts Co-authored-by: isaacs --- packages/node/src/sdk/index.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/node/src/sdk/index.ts b/packages/node/src/sdk/index.ts index 38b7d46355f3..4882a4dabba8 100644 --- a/packages/node/src/sdk/index.ts +++ b/packages/node/src/sdk/index.ts @@ -163,10 +163,12 @@ function _init( // runtime with `enableRuntimeChannelInjection: false`, or at build time via the bundler plugins' // `bundleSizeOptimizations.excludeChannelInjection` (which tree-shakes this whole block away). // Install as early as possible, before the app imports its instrumented modules. - const useChannelInjection = + if ( (typeof __SENTRY_CHANNEL_INJECTION__ === 'undefined' || __SENTRY_CHANNEL_INJECTION__) && - options.enableRuntimeChannelInjection !== false; - if (useChannelInjection) { + options.enableRuntimeChannelInjection !== false + ) { + registerDiagnosticsChannelInjection(); + } registerDiagnosticsChannelInjection(); } From 00566b92f90a17565579d9ff1dec3343dd07c78d Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 26 Aug 2026 09:37:29 +0200 Subject: [PATCH 4/4] fix oops --- packages/node/src/sdk/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/node/src/sdk/index.ts b/packages/node/src/sdk/index.ts index 4882a4dabba8..623a1ec3805c 100644 --- a/packages/node/src/sdk/index.ts +++ b/packages/node/src/sdk/index.ts @@ -169,8 +169,6 @@ function _init( ) { registerDiagnosticsChannelInjection(); } - registerDiagnosticsChannelInjection(); - } // Only use Node SDK defaults if none provided. const defaultIntegrations = options.defaultIntegrations ?? getDefaultIntegrationsImpl(optionsWithResolvedTracing);