From d609f139b5800e91b6068fe57571225a78579600 Mon Sep 17 00:00:00 2001 From: lazerg Date: Thu, 29 Oct 2026 11:57:07 +0500 Subject: [PATCH 1/6] feat(server-utils): Always warn on Bun when no channel injection is detected Co-Authored-By: Claude Opus 5.5 --- .../server-utils/src/orchestrion/detect.ts | 13 +++++- .../test/orchestrion/detect.test.ts | 44 ++++++++++++++++++- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/packages/server-utils/src/orchestrion/detect.ts b/packages/server-utils/src/orchestrion/detect.ts index 572afea7b5f5..13e2d9abc49f 100644 --- a/packages/server-utils/src/orchestrion/detect.ts +++ b/packages/server-utils/src/orchestrion/detect.ts @@ -1,4 +1,4 @@ -import { debug, GLOBAL_OBJ } from '@sentry/core'; +import { consoleSandbox, debug, GLOBAL_OBJ } from '@sentry/core'; /** * The module names (e.g. `mysql`, `@hapi/hapi`) orchestrion has already injected @@ -35,6 +35,17 @@ export function detectOrchestrionSetup(): void { const { runtime, bundler } = GLOBAL_OBJ.__SENTRY_ORCHESTRION__ ?? {}; if (!runtime && !bundler) { + if (typeof (globalThis as { Bun?: unknown }).Bun !== 'undefined') { + consoleSandbox(() => { + // oxlint-disable-next-line no-console + console.warn( + '[Sentry] No diagnostics-channel injection detected. Channel-based integrations will not record ' + + 'spans or capture errors under `bun run`. Build your app with `@sentry/bun/plugin` to enable them.', + ); + }); + return; + } + debug.warn( '[Sentry] No diagnostics-channel injection detected. Channel-based integrations ' + 'will not record spans. Make sure the diagnostics channels are injected ' + diff --git a/packages/server-utils/test/orchestrion/detect.test.ts b/packages/server-utils/test/orchestrion/detect.test.ts index 97edd5223142..a308f6a8ab52 100644 --- a/packages/server-utils/test/orchestrion/detect.test.ts +++ b/packages/server-utils/test/orchestrion/detect.test.ts @@ -1,6 +1,6 @@ import { GLOBAL_OBJ } from '@sentry/core'; -import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { getOrchestrionInjectedModules } from '../../src/orchestrion/detect'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { detectOrchestrionSetup, getOrchestrionInjectedModules } from '../../src/orchestrion/detect'; describe('getOrchestrionInjectedModules', () => { beforeEach(() => { @@ -25,3 +25,43 @@ describe('getOrchestrionInjectedModules', () => { expect(getOrchestrionInjectedModules()).toEqual(['pg']); }); }); + +describe('detectOrchestrionSetup', () => { + beforeEach(() => { + delete GLOBAL_OBJ.__SENTRY_ORCHESTRION__; + }); + + afterEach(() => { + delete GLOBAL_OBJ.__SENTRY_ORCHESTRION__; + vi.unstubAllGlobals(); + vi.restoreAllMocks(); + }); + + it('always warns on Bun when no injection is detected', () => { + vi.stubGlobal('Bun', {}); + const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined); + + detectOrchestrionSetup(); + + expect(warn).toHaveBeenCalledTimes(1); + expect(warn.mock.calls[0]?.[0]).toContain('@sentry/bun/plugin'); + }); + + it('does not warn on Bun when the bundler plugin ran', () => { + vi.stubGlobal('Bun', {}); + GLOBAL_OBJ.__SENTRY_ORCHESTRION__ = { bundler: new Set() }; + const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined); + + detectOrchestrionSetup(); + + expect(warn).not.toHaveBeenCalled(); + }); + + it('does not warn outside Bun when debug is off', () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined); + + detectOrchestrionSetup(); + + expect(warn).not.toHaveBeenCalled(); + }); +}); From 4a37529d45c9347707cd3f82bc6d278108827df6 Mon Sep 17 00:00:00 2001 From: lazerg Date: Thu, 29 Oct 2026 12:15:52 +0500 Subject: [PATCH 2/6] test(bun): Cover the missing channel injection warning under bun run Co-Authored-By: Claude Opus 5.5 --- .../suites/no-channel-injection/scenario.ts | 6 ++++++ .../suites/no-channel-injection/test.ts | 13 +++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 dev-packages/bun-integration-tests/suites/no-channel-injection/scenario.ts create mode 100644 dev-packages/bun-integration-tests/suites/no-channel-injection/test.ts diff --git a/dev-packages/bun-integration-tests/suites/no-channel-injection/scenario.ts b/dev-packages/bun-integration-tests/suites/no-channel-injection/scenario.ts new file mode 100644 index 000000000000..0ce01f05a57b --- /dev/null +++ b/dev-packages/bun-integration-tests/suites/no-channel-injection/scenario.ts @@ -0,0 +1,6 @@ +import * as Sentry from '@sentry/bun'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + tracesSampleRate: 1.0, +}); diff --git a/dev-packages/bun-integration-tests/suites/no-channel-injection/test.ts b/dev-packages/bun-integration-tests/suites/no-channel-injection/test.ts new file mode 100644 index 000000000000..0f47f744e035 --- /dev/null +++ b/dev-packages/bun-integration-tests/suites/no-channel-injection/test.ts @@ -0,0 +1,13 @@ +import { spawnSync } from 'child_process'; +import { join } from 'path'; +import { describe, expect, it } from 'vitest'; + +describe('no channel injection (Bun)', () => { + it('warns without `debug` when the app runs unbuilt with `bun run`', () => { + const res = spawnSync('bun', ['run', join(__dirname, 'scenario.ts')], { cwd: __dirname, encoding: 'utf8' }); + + expect(res.status, `run failed:\nstderr:\n${res.stderr}\nstdout:\n${res.stdout}`).toBe(0); + expect(`${res.stdout}${res.stderr}`).toContain('No diagnostics-channel injection detected'); + expect(`${res.stdout}${res.stderr}`).toContain('@sentry/bun/plugin'); + }); +}); From 4d915f4d92eee1223b623f5ad78589b957d539b5 Mon Sep 17 00:00:00 2001 From: isaacs Date: Wed, 4 Nov 2026 17:02:08 -0800 Subject: [PATCH 3/6] fix(server-utils): Only warn on Bun when a channel integration is installed The Bun warning for missing channel injection showed even when no integration uses the channels, for example with `initWithoutDefaultIntegrations()` or `defaultIntegrations: false`. In those setups nothing is lost, the message is false, and the user has no way to turn it off. Every integration that needs channel injection subscribes through `invokeOrchestrionInstrumentation()`. The helper now sets `channelIntegrations` on the orchestrion marker, and `detectOrchestrionSetup()` shows the Bun warning only when that flag is set. This needs no list of integration names to keep in sync. Integrations that do not need injection, such as Fastify, do not call the helper and do not set the flag. Co-Authored-By: Claude Opus 5.5 --- .../scenario-no-integrations.ts | 6 ++++++ .../suites/no-channel-injection/test.ts | 17 +++++++++++++---- packages/core/src/utils/worldwide.ts | 5 +++++ packages/server-utils/src/orchestrion/detect.ts | 4 ++-- .../src/orchestrion/instrumentation.ts | 2 ++ .../test/orchestrion/detect.test.ts | 15 +++++++++++++-- .../test/orchestrion/instrumentation.test.ts | 6 ++++++ 7 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 dev-packages/bun-integration-tests/suites/no-channel-injection/scenario-no-integrations.ts diff --git a/dev-packages/bun-integration-tests/suites/no-channel-injection/scenario-no-integrations.ts b/dev-packages/bun-integration-tests/suites/no-channel-injection/scenario-no-integrations.ts new file mode 100644 index 000000000000..d7f5b49d899f --- /dev/null +++ b/dev-packages/bun-integration-tests/suites/no-channel-injection/scenario-no-integrations.ts @@ -0,0 +1,6 @@ +import * as Sentry from '@sentry/bun'; + +Sentry.initWithoutDefaultIntegrations({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + tracesSampleRate: 1.0, +}); diff --git a/dev-packages/bun-integration-tests/suites/no-channel-injection/test.ts b/dev-packages/bun-integration-tests/suites/no-channel-injection/test.ts index 0f47f744e035..0a8953ea3aba 100644 --- a/dev-packages/bun-integration-tests/suites/no-channel-injection/test.ts +++ b/dev-packages/bun-integration-tests/suites/no-channel-injection/test.ts @@ -2,12 +2,21 @@ import { spawnSync } from 'child_process'; import { join } from 'path'; import { describe, expect, it } from 'vitest'; +function runScenario(file: string): string { + const res = spawnSync('bun', ['run', join(__dirname, file)], { cwd: __dirname, encoding: 'utf8' }); + expect(res.status, `run failed:\nstderr:\n${res.stderr}\nstdout:\n${res.stdout}`).toBe(0); + return `${res.stdout}${res.stderr}`; +} + describe('no channel injection (Bun)', () => { it('warns without `debug` when the app runs unbuilt with `bun run`', () => { - const res = spawnSync('bun', ['run', join(__dirname, 'scenario.ts')], { cwd: __dirname, encoding: 'utf8' }); + const output = runScenario('scenario.ts'); + + expect(output).toContain('No diagnostics-channel injection detected'); + expect(output).toContain('@sentry/bun/plugin'); + }); - expect(res.status, `run failed:\nstderr:\n${res.stderr}\nstdout:\n${res.stdout}`).toBe(0); - expect(`${res.stdout}${res.stderr}`).toContain('No diagnostics-channel injection detected'); - expect(`${res.stdout}${res.stderr}`).toContain('@sentry/bun/plugin'); + it('does not warn when no channel integration is installed', () => { + expect(runScenario('scenario-no-integrations.ts')).not.toContain('No diagnostics-channel injection detected'); }); }); diff --git a/packages/core/src/utils/worldwide.ts b/packages/core/src/utils/worldwide.ts index 2ca3d60bc21d..1f0944f0a74a 100644 --- a/packages/core/src/utils/worldwide.ts +++ b/packages/core/src/utils/worldwide.ts @@ -95,6 +95,11 @@ export type InternalGlobal = { * Dedupes the one-time warning and short-circuits repeat calls. */ runtimeUnavailable?: boolean; + /** + * Set when an integration asks to subscribe to orchestrion-injected + * channels, so a missing injection is only reported when it loses data. + */ + channelIntegrations?: boolean; /** * Module namespaces stashed by build-time provider imports, keyed by module * name (e.g. `@mastra/observability`). A bundler plugin can splice a static diff --git a/packages/server-utils/src/orchestrion/detect.ts b/packages/server-utils/src/orchestrion/detect.ts index 13e2d9abc49f..be38088c9837 100644 --- a/packages/server-utils/src/orchestrion/detect.ts +++ b/packages/server-utils/src/orchestrion/detect.ts @@ -32,10 +32,10 @@ export function getOrchestrionInjectedModules(): string[] { * is fine. */ export function detectOrchestrionSetup(): void { - const { runtime, bundler } = GLOBAL_OBJ.__SENTRY_ORCHESTRION__ ?? {}; + const { runtime, bundler, channelIntegrations } = GLOBAL_OBJ.__SENTRY_ORCHESTRION__ ?? {}; if (!runtime && !bundler) { - if (typeof (globalThis as { Bun?: unknown }).Bun !== 'undefined') { + if (channelIntegrations && typeof (globalThis as { Bun?: unknown }).Bun !== 'undefined') { consoleSandbox(() => { // oxlint-disable-next-line no-console console.warn( diff --git a/packages/server-utils/src/orchestrion/instrumentation.ts b/packages/server-utils/src/orchestrion/instrumentation.ts index 0dbddefc4da7..bf793330c9eb 100644 --- a/packages/server-utils/src/orchestrion/instrumentation.ts +++ b/packages/server-utils/src/orchestrion/instrumentation.ts @@ -58,6 +58,8 @@ export function invokeOrchestrionInstrumentation { it('always warns on Bun when no injection is detected', () => { vi.stubGlobal('Bun', {}); + GLOBAL_OBJ.__SENTRY_ORCHESTRION__ = { channelIntegrations: true }; const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined); detectOrchestrionSetup(); expect(warn).toHaveBeenCalledTimes(1); - expect(warn.mock.calls[0]?.[0]).toContain('@sentry/bun/plugin'); + expect(warn).toHaveBeenCalledWith(expect.stringContaining('@sentry/bun/plugin')); + }); + + it('does not warn on Bun when no channel integration is installed', () => { + vi.stubGlobal('Bun', {}); + const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined); + + detectOrchestrionSetup(); + + expect(warn).not.toHaveBeenCalled(); }); it('does not warn on Bun when the bundler plugin ran', () => { vi.stubGlobal('Bun', {}); - GLOBAL_OBJ.__SENTRY_ORCHESTRION__ = { bundler: new Set() }; + GLOBAL_OBJ.__SENTRY_ORCHESTRION__ = { bundler: new Set(), channelIntegrations: true }; const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined); detectOrchestrionSetup(); @@ -58,6 +68,7 @@ describe('detectOrchestrionSetup', () => { }); it('does not warn outside Bun when debug is off', () => { + GLOBAL_OBJ.__SENTRY_ORCHESTRION__ = { channelIntegrations: true }; const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined); detectOrchestrionSetup(); diff --git a/packages/server-utils/test/orchestrion/instrumentation.test.ts b/packages/server-utils/test/orchestrion/instrumentation.test.ts index 07c61fd5bb7a..0322105fd948 100644 --- a/packages/server-utils/test/orchestrion/instrumentation.test.ts +++ b/packages/server-utils/test/orchestrion/instrumentation.test.ts @@ -46,6 +46,12 @@ describe('invokeOrchestrionInstrumentation', () => { delete GLOBAL_OBJ.__SENTRY_ORCHESTRION__; }); + it('records on the marker that a channel integration is installed', () => { + invokeOrchestrionInstrumentation(makeClient() as never, ['mysql'], vi.fn(), []); + + expect(GLOBAL_OBJ.__SENTRY_ORCHESTRION__?.channelIntegrations).toBe(true); + }); + it('subscribes immediately when a module is already injected', () => { GLOBAL_OBJ.__SENTRY_ORCHESTRION__ = { runtime: ['mysql'] }; const client = makeClient(); From e50c8f3860090ea29ccafbe754039adbf2327b0b Mon Sep 17 00:00:00 2001 From: isaacs Date: Wed, 4 Nov 2026 17:06:38 -0800 Subject: [PATCH 4/6] fix(server-utils): Do not name `bun run` in the missing channel injection warning The Bun warning fires whenever no injection marker exists, not only under `bun run`. A `bun build --compile` binary or a `Bun.build` bundle made without `@sentry/bun/plugin` gets the same warning, and the advice to build with the plugin is right for those too. Only the `bun run` phrase was wrong, so the message now gives the real cause: Bun cannot instrument modules at runtime. Co-Authored-By: Claude Opus 5.5 --- packages/server-utils/src/orchestrion/detect.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/server-utils/src/orchestrion/detect.ts b/packages/server-utils/src/orchestrion/detect.ts index be38088c9837..e442c0ce9004 100644 --- a/packages/server-utils/src/orchestrion/detect.ts +++ b/packages/server-utils/src/orchestrion/detect.ts @@ -40,7 +40,8 @@ export function detectOrchestrionSetup(): void { // oxlint-disable-next-line no-console console.warn( '[Sentry] No diagnostics-channel injection detected. Channel-based integrations will not record ' + - 'spans or capture errors under `bun run`. Build your app with `@sentry/bun/plugin` to enable them.', + 'spans or capture errors, because Bun cannot instrument modules at runtime. Build your app with ' + + '`@sentry/bun/plugin` to enable them.', ); }); return; From 8e27791fe4fd32cb0710df8fcfd232c39993a14e Mon Sep 17 00:00:00 2001 From: isaacs Date: Wed, 4 Nov 2026 17:16:22 -0800 Subject: [PATCH 5/6] fix(server-utils): Show the Bun missing channel injection warning once Node's `_init` still replaces the client on a repeated `Sentry.init()`, and it calls `detectOrchestrionSetup()` each time. The Bun warning is always on, so a second `init()` showed it again. The warning now sets `warnedBunRunNotInjected` on the orchestrion marker and does not show again while it is set. The flag lives on the marker, not in module state, so duplicate SDK copies in one process also warn only once. Co-Authored-By: Claude Opus 5.5 --- packages/core/src/utils/worldwide.ts | 5 +++++ .../server-utils/src/orchestrion/detect.ts | 22 +++++++++++-------- .../test/orchestrion/detect.test.ts | 11 ++++++++++ 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/packages/core/src/utils/worldwide.ts b/packages/core/src/utils/worldwide.ts index 1f0944f0a74a..fd88b420a2bc 100644 --- a/packages/core/src/utils/worldwide.ts +++ b/packages/core/src/utils/worldwide.ts @@ -100,6 +100,11 @@ export type InternalGlobal = { * channels, so a missing injection is only reported when it loses data. */ channelIntegrations?: boolean; + /** + * Set once the Bun warning for missing channel injection has been shown. + * Lives on the marker so that duplicate SDK copies warn only once. + */ + warnedBunRunNotInjected?: boolean; /** * Module namespaces stashed by build-time provider imports, keyed by module * name (e.g. `@mastra/observability`). A bundler plugin can splice a static diff --git a/packages/server-utils/src/orchestrion/detect.ts b/packages/server-utils/src/orchestrion/detect.ts index e442c0ce9004..c0674371e50a 100644 --- a/packages/server-utils/src/orchestrion/detect.ts +++ b/packages/server-utils/src/orchestrion/detect.ts @@ -32,18 +32,22 @@ export function getOrchestrionInjectedModules(): string[] { * is fine. */ export function detectOrchestrionSetup(): void { - const { runtime, bundler, channelIntegrations } = GLOBAL_OBJ.__SENTRY_ORCHESTRION__ ?? {}; + const marker = GLOBAL_OBJ.__SENTRY_ORCHESTRION__ ?? {}; + const { runtime, bundler, channelIntegrations } = marker; if (!runtime && !bundler) { if (channelIntegrations && typeof (globalThis as { Bun?: unknown }).Bun !== 'undefined') { - consoleSandbox(() => { - // oxlint-disable-next-line no-console - console.warn( - '[Sentry] No diagnostics-channel injection detected. Channel-based integrations will not record ' + - 'spans or capture errors, because Bun cannot instrument modules at runtime. Build your app with ' + - '`@sentry/bun/plugin` to enable them.', - ); - }); + if (!marker.warnedBunRunNotInjected) { + marker.warnedBunRunNotInjected = true; + consoleSandbox(() => { + // oxlint-disable-next-line no-console + console.warn( + '[Sentry] No diagnostics-channel injection detected. Channel-based integrations will not record ' + + 'spans or capture errors, because Bun cannot instrument modules at runtime. Build your app with ' + + '`@sentry/bun/plugin` to enable them.', + ); + }); + } return; } diff --git a/packages/server-utils/test/orchestrion/detect.test.ts b/packages/server-utils/test/orchestrion/detect.test.ts index eca5e294ed67..041971d83ba0 100644 --- a/packages/server-utils/test/orchestrion/detect.test.ts +++ b/packages/server-utils/test/orchestrion/detect.test.ts @@ -48,6 +48,17 @@ describe('detectOrchestrionSetup', () => { expect(warn).toHaveBeenCalledWith(expect.stringContaining('@sentry/bun/plugin')); }); + it('warns on Bun only once when called again, as on a repeated `init()`', () => { + vi.stubGlobal('Bun', {}); + GLOBAL_OBJ.__SENTRY_ORCHESTRION__ = { channelIntegrations: true }; + const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined); + + detectOrchestrionSetup(); + detectOrchestrionSetup(); + + expect(warn).toHaveBeenCalledTimes(1); + }); + it('does not warn on Bun when no channel integration is installed', () => { vi.stubGlobal('Bun', {}); const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined); From e4b6c978643f3648a6313527b3be062ab42ed38f Mon Sep 17 00:00:00 2001 From: isaacs Date: Wed, 4 Nov 2026 21:03:21 -0800 Subject: [PATCH 6/6] test(node): Allow the Bun missing channel injection warning in error output checks The Bun integration jobs run the Node suites with `bun run` and without `@sentry/bun/plugin`, so the SDK now warns on stderr that channel injection is missing. `ensureNoErrorOutput()` failed on that warning in `esm/modules-integration` and `tracing/orchestrion-registration-only`. When `RUNTIME` is `bun`, the runner now drops that warning line before it checks stderr. Other output on the same chunk still fails the test. Co-Authored-By: Claude Opus 5.5 --- .../utils/runner/createRunner.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/dev-packages/node-integration-tests/utils/runner/createRunner.ts b/dev-packages/node-integration-tests/utils/runner/createRunner.ts index b592869e9aca..db07a127ff12 100644 --- a/dev-packages/node-integration-tests/utils/runner/createRunner.ts +++ b/dev-packages/node-integration-tests/utils/runner/createRunner.ts @@ -665,8 +665,18 @@ export function createRunner(...paths: string[]) { if (process.env.DEBUG) log('stderr line', output); + // Bun runs these scenarios without the Bun build plugin, so the SDK warns that channel + // injection is missing. That warning is expected here and is not an error. + const unexpectedOutput = + process.env.RUNTIME === 'bun' + ? output + .split('\n') + .filter(line => !line.includes('[Sentry] No diagnostics-channel injection detected.')) + .join('\n') + : output; + // Ignore deprecation warnings for this purpose - if (ensureNoErrorOutput && !`${output}`.includes('DeprecationWarning:')) { + if (ensureNoErrorOutput && unexpectedOutput.trim() && !unexpectedOutput.includes('DeprecationWarning:')) { complete(new Error(`Expected no error output but got: '${output}'`)); } });