From 6eda02cdaf11218104bf7ce6ab57435d375f2926 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 14:00:52 +0000 Subject: [PATCH 1/7] fix(cli): Drop stale SDK SIGTERM listeners on telemetry re-init Under VERCEL the bundled light SDK registers a SIGTERM flush handler on every Sentry.init() and never removes it. Library-mode commands re-init per invocation, so in-process callers like the bundler plugins leak one listener per command and hit MaxListenersExceededWarning after 10. Track the listeners each init adds and remove them before the next init, keeping only the current client's flush handler. Fixes #1692 --- packages/cli/src/lib/telemetry.ts | 15 +++++++++ packages/cli/test/lib/telemetry.test.ts | 45 +++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/packages/cli/src/lib/telemetry.ts b/packages/cli/src/lib/telemetry.ts index fa1ec7fe9..278be81a5 100644 --- a/packages/cli/src/lib/telemetry.ts +++ b/packages/cli/src/lib/telemetry.ts @@ -450,6 +450,13 @@ const hasGetSystemErrorMap = (() => { /** Current beforeExit handler, tracked so it can be replaced on re-init */ let currentBeforeExitHandler: (() => void) | null = null; +/** + * SIGTERM listeners registered by the last `Sentry.init()`. Under `VERCEL`, + * the light SDK adds an anonymous flush handler per init and never removes + * it, so repeated library-mode inits leak one listener each. + */ +let sdkSigtermListeners: NodeJS.SignalsListener[] = []; + /** Match all SaaS regional URLs (us.sentry.io, de.sentry.io, o1234.ingest.us.sentry.io, etc.) */ const SENTRY_SAAS_SUBDOMAIN_RE = /^https:\/\/[^/]*\.sentry\.io(\/|$)/; @@ -587,6 +594,10 @@ export function initSentry( // that keep the event loop alive and prevent the process from exiting. // close(0) removes listeners synchronously; we don't need to await the flush. Sentry.getClient()?.close(0); + for (const listener of sdkSigtermListeners) { + process.removeListener("SIGTERM", listener); + } + const preInitSigterm = new Set(process.listeners("SIGTERM")); const client = Sentry.init({ dsn: SENTRY_CLI_DSN, @@ -694,6 +705,10 @@ export function initSentry( }, }); + sdkSigtermListeners = process + .listeners("SIGTERM") + .filter((listener) => !preInitSigterm.has(listener)); + // Always remove our own previous handler on re-init. if (currentBeforeExitHandler) { process.removeListener("beforeExit", currentBeforeExitHandler); diff --git a/packages/cli/test/lib/telemetry.test.ts b/packages/cli/test/lib/telemetry.test.ts index e0873dc39..2b61fd76b 100644 --- a/packages/cli/test/lib/telemetry.test.ts +++ b/packages/cli/test/lib/telemetry.test.ts @@ -107,6 +107,51 @@ describe("initSentry", () => { const client = initSentry(true); expect(client?.getOptions().release).toBe("0.0.0-dev"); }); + + describe("under VERCEL", () => { + let originalVercel: string | undefined; + let preTestSigterm: Set; + + beforeEach(() => { + originalVercel = process.env.VERCEL; + process.env.VERCEL = "1"; + preTestSigterm = new Set(process.rawListeners("SIGTERM")); + }); + + afterEach(() => { + if (originalVercel === undefined) { + delete process.env.VERCEL; + } else { + process.env.VERCEL = originalVercel; + } + for (const listener of process.rawListeners("SIGTERM")) { + if (!preTestSigterm.has(listener)) { + process.removeListener( + "SIGTERM", + listener as (...args: unknown[]) => void, + ); + } + } + }); + + test("keeps one SDK SIGTERM listener across repeated inits", () => { + const baseline = process.listenerCount("SIGTERM"); + for (let i = 0; i < 15; i++) { + initSentry(i % 2 === 0, { libraryMode: true }); + } + expect(process.listenerCount("SIGTERM")).toBe(baseline + 1); + }); + + test("preserves SIGTERM listeners it did not register", () => { + const hostListener = () => { + // host-owned handler + }; + process.on("SIGTERM", hostListener); + initSentry(true, { libraryMode: true }); + initSentry(true, { libraryMode: true }); + expect(process.listeners("SIGTERM")).toContain(hostListener); + }); + }); }); describe("withTelemetry", () => { From 54084ad2fc6620c4de4fc0f3ed8f833639510d37 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 14:09:34 +0000 Subject: [PATCH 2/7] fix(cli): Patch light SDK to register the Vercel SIGTERM listener once Move the fix from a telemetry.ts workaround into the existing @sentry/node-core patch. The light init() now registers one SIGTERM listener that flushes the latest client, mirroring getsentry/sentry-javascript#25146 for @sentry/node. --- .../patches/@sentry%2Fnode-core@10.63.0.patch | 60 +++++++++++++++++++ packages/cli/src/lib/telemetry.ts | 15 ----- pnpm-lock.yaml | 6 +- 3 files changed, 63 insertions(+), 18 deletions(-) diff --git a/packages/cli/patches/@sentry%2Fnode-core@10.63.0.patch b/packages/cli/patches/@sentry%2Fnode-core@10.63.0.patch index 88bc631e2..1ddb1e887 100644 --- a/packages/cli/patches/@sentry%2Fnode-core@10.63.0.patch +++ b/packages/cli/patches/@sentry%2Fnode-core@10.63.0.patch @@ -64,6 +64,36 @@ index 8fb4980846f783f7e352e7abd056bb95b8d86ea6..5514ac17921b832eb31c114e9bdf8999 -exports.wrapMcpServerWithSentry = core.wrapMcpServerWithSentry; -exports.zodErrorsIntegration = core.zodErrorsIntegration; //# sourceMappingURL=index.js.map +diff --git a/build/cjs/light/sdk.js b/build/cjs/light/sdk.js +index 1e4c21c7825cb415e1e5b6371afc4af9afc90d7e..592979803554012e35ecf7dca50379744797a797 100644 +--- a/build/cjs/light/sdk.js ++++ b/build/cjs/light/sdk.js +@@ -22,6 +22,8 @@ const client = require('./client.js'); + const httpIntegration = require('./integrations/httpIntegration.js'); + const nativeNodeFetchIntegration = require('./integrations/nativeNodeFetchIntegration.js'); + ++// A repeated `init()` replaces the client, so the SIGTERM listener is registered once and flushes the latest client. ++let vercelSigtermClient; + function getDefaultIntegrations() { + return [ + // Common +@@ -81,9 +83,13 @@ function _init(_options = {}, getDefaultIntegrationsImpl) { + client$1.startClientReportTracking(); + updateScopeFromEnvVariables(); + if (process.env.VERCEL) { +- process.on("SIGTERM", async () => { +- await client$1.flush(200); +- }); ++ const isRegistered = !!vercelSigtermClient; ++ vercelSigtermClient = client$1; ++ if (!isRegistered) { ++ process.on("SIGTERM", async () => { ++ await vercelSigtermClient?.flush(200); ++ }); ++ } + } + return client$1; + } diff --git a/build/esm/light/index.js b/build/esm/light/index.js index f9e2a0818600f096bb980db7330338829256416d..a58d0aba64f228b9ced5291797507e8b1dadc111 100644 --- a/build/esm/light/index.js @@ -75,3 +105,33 @@ index f9e2a0818600f096bb980db7330338829256416d..a58d0aba64f228b9ced5291797507e8b -export { SDK_VERSION, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, Scope, addBreadcrumb, addEventProcessor, addIntegration, bindScopeToEmitter, captureCheckIn, captureConsoleIntegration, captureEvent, captureException, captureFeedback, captureMessage, captureSession, close, consoleLoggingIntegration, continueTrace, createConsolaReporter, createTransport, dedupeIntegration, endSession, envToBool, eventFiltersIntegration, extraErrorDataIntegration, featureFlagsIntegration, flush, functionToStringIntegration, getActiveSpan, getClient, getCurrentScope, getGlobalScope, getIsolationScope, getRequestUrl, getRootSpan, getSpanDescendants, getSpanStatusFromHttpCode, getTraceData, getTraceMetaTags, instrumentSupabaseClient, isEnabled, isInitialized, lastEventId, linkedErrorsIntegration, metrics, parameterize, profiler, requestDataIntegration, rewriteFramesIntegration, setAttribute, setAttributes, setContext, setCurrentClient, setExtra, setExtras, setHttpStatus, setMeasurement, setTag, setTags, setUser, spanStreamingIntegration, spanToBaggageHeader, spanToJSON, spanToTraceHeader, startInactiveSpan, startNewTrace, startSession, startSpan, startSpanManual, supabaseIntegration, suppressTracing, trpcMiddleware, updateSpanName, withActiveSpan, withIsolationScope, withMonitor, withScope, withStreamedSpan, wrapMcpServerWithSentry, zodErrorsIntegration } from '@sentry/core'; +export { SDK_VERSION, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, Scope, addBreadcrumb, addEventProcessor, addIntegration, bindScopeToEmitter, captureCheckIn, captureEvent, captureException, captureFeedback, captureMessage, captureSession, close, consoleLoggingIntegration, continueTrace, createConsolaReporter, createTransport, endSession, envToBool, eventFiltersIntegration, flush, functionToStringIntegration, getActiveSpan, getClient, getCurrentScope, getGlobalScope, getIsolationScope, getRequestUrl, getRootSpan, getSpanDescendants, getSpanStatusFromHttpCode, getTraceData, getTraceMetaTags, isEnabled, isInitialized, lastEventId, linkedErrorsIntegration, metrics, parameterize, requestDataIntegration, setAttribute, setAttributes, setContext, setCurrentClient, setExtra, setExtras, setHttpStatus, setMeasurement, setTag, setTags, setUser, spanToBaggageHeader, spanToJSON, spanToTraceHeader, startInactiveSpan, startNewTrace, startSession, startSpan, startSpanManual, suppressTracing, updateSpanName, withActiveSpan, withIsolationScope, withMonitor, withScope } from '@sentry/core'; //# sourceMappingURL=index.js.map +diff --git a/build/esm/light/sdk.js b/build/esm/light/sdk.js +index c8cef838dcc6bd8148681f68e7af8437e4db0987..ebb07284bb306b6a7484f3602ffa5042467ebace 100644 +--- a/build/esm/light/sdk.js ++++ b/build/esm/light/sdk.js +@@ -20,6 +20,8 @@ import { LightNodeClient } from './client.js'; + import { httpIntegration } from './integrations/httpIntegration.js'; + import { nativeNodeFetchIntegration } from './integrations/nativeNodeFetchIntegration.js'; + ++// A repeated `init()` replaces the client, so the SIGTERM listener is registered once and flushes the latest client. ++let vercelSigtermClient; + function getDefaultIntegrations() { + return [ + // Common +@@ -79,9 +81,13 @@ function _init(_options = {}, getDefaultIntegrationsImpl) { + client.startClientReportTracking(); + updateScopeFromEnvVariables(); + if (process.env.VERCEL) { +- process.on("SIGTERM", async () => { +- await client.flush(200); +- }); ++ const isRegistered = !!vercelSigtermClient; ++ vercelSigtermClient = client; ++ if (!isRegistered) { ++ process.on("SIGTERM", async () => { ++ await vercelSigtermClient?.flush(200); ++ }); ++ } + } + return client; + } diff --git a/packages/cli/src/lib/telemetry.ts b/packages/cli/src/lib/telemetry.ts index 278be81a5..fa1ec7fe9 100644 --- a/packages/cli/src/lib/telemetry.ts +++ b/packages/cli/src/lib/telemetry.ts @@ -450,13 +450,6 @@ const hasGetSystemErrorMap = (() => { /** Current beforeExit handler, tracked so it can be replaced on re-init */ let currentBeforeExitHandler: (() => void) | null = null; -/** - * SIGTERM listeners registered by the last `Sentry.init()`. Under `VERCEL`, - * the light SDK adds an anonymous flush handler per init and never removes - * it, so repeated library-mode inits leak one listener each. - */ -let sdkSigtermListeners: NodeJS.SignalsListener[] = []; - /** Match all SaaS regional URLs (us.sentry.io, de.sentry.io, o1234.ingest.us.sentry.io, etc.) */ const SENTRY_SAAS_SUBDOMAIN_RE = /^https:\/\/[^/]*\.sentry\.io(\/|$)/; @@ -594,10 +587,6 @@ export function initSentry( // that keep the event loop alive and prevent the process from exiting. // close(0) removes listeners synchronously; we don't need to await the flush. Sentry.getClient()?.close(0); - for (const listener of sdkSigtermListeners) { - process.removeListener("SIGTERM", listener); - } - const preInitSigterm = new Set(process.listeners("SIGTERM")); const client = Sentry.init({ dsn: SENTRY_CLI_DSN, @@ -705,10 +694,6 @@ export function initSentry( }, }); - sdkSigtermListeners = process - .listeners("SIGTERM") - .filter((listener) => !preInitSigterm.has(listener)); - // Always remove our own previous handler on re-init. if (currentBeforeExitHandler) { process.removeListener("beforeExit", currentBeforeExitHandler); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 135443c29..691c5ef46 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -204,7 +204,7 @@ patchedDependencies: '@ai-sdk/provider-utils@2.2.8': 4649a74595006c4a262d453549e091db331ade06b4f4320957af1b2c659b8ee3 '@cloudflare/workers-oauth-provider@0.10.3': 34910b67929c1888b1492384c514d6baeedc4a439f66e21375dee5af404834f8 '@sentry/core@10.63.0': e663994979ff877a26ab6d4dea5968fbaee4ccdfdeb85623983535a572678940 - '@sentry/node-core@10.63.0': 0a5f08471bd72cb833a2b36220c96001d966e5c390d71aaf598fdef75b552640 + '@sentry/node-core@10.63.0': a537ad4fb79b41d54b6294108829d470d10a45d3895041f16ab11168de0f1983 '@stricli/core@1.2.8': cbf047581d38be04108d3921321c14a778272c24004da8110d4498d4f2a6b2e1 importers: @@ -481,7 +481,7 @@ importers: version: 10.65.0(@opentelemetry/core@2.11.0(@opentelemetry/api@1.9.1)) '@sentry/node-core': specifier: 10.63.0 - version: 10.63.0(patch_hash=0a5f08471bd72cb833a2b36220c96001d966e5c390d71aaf598fdef75b552640)(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.220.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.11.0(@opentelemetry/api@1.9.1)) + version: 10.63.0(patch_hash=a537ad4fb79b41d54b6294108829d470d10a45d3895041f16ab11168de0f1983)(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.220.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.11.0(@opentelemetry/api@1.9.1)) '@sentry/sqlish': specifier: ^1.0.1 version: 1.0.1(react@19.2.8) @@ -12787,7 +12787,7 @@ snapshots: dependencies: '@sentry/core': 11.6.0 - '@sentry/node-core@10.63.0(patch_hash=0a5f08471bd72cb833a2b36220c96001d966e5c390d71aaf598fdef75b552640)(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.220.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.11.0(@opentelemetry/api@1.9.1))': + '@sentry/node-core@10.63.0(patch_hash=a537ad4fb79b41d54b6294108829d470d10a45d3895041f16ab11168de0f1983)(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.220.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.11.0(@opentelemetry/api@1.9.1))': dependencies: '@sentry/conventions': 0.12.0 '@sentry/core': 10.63.0(patch_hash=e663994979ff877a26ab6d4dea5968fbaee4ccdfdeb85623983535a572678940) From f0f2b51e65eca663cd958c943ca9ea8458dae28a Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:43:24 +0000 Subject: [PATCH 3/7] fix(cli): Move the CLI to the catalog Sentry SDK (v11) The CLI pinned @sentry/core, @sentry/node and @sentry/node-core on v10, and v10's light init() adds a SIGTERM listener per init under Vercel. v11 already registers that listener once, so switch the CLI to the workspace catalog versions and drop the v10 patches. v11 removes @sentry/node-core/light, so the CLI now inits @sentry/node without its default integrations and passes the v10 light set explicitly. Other v11 defaults are pinned back to v10 behavior: static traces, the v10 data collection baseline, no attached stack traces, no runtime channel injection, and no logs in library mode. Also binds the client after init, because v11 loses it when init runs inside a pending span, and imports the consola reporter statically so the bundle no longer requires a runtime @sentry/node. --- packages/cli/package.json | 5 +- .../cli/patches/@sentry%2Fcore@10.63.0.patch | 309 ------------------ .../patches/@sentry%2Fnode-core@10.63.0.patch | 137 -------- packages/cli/script/bench.ts | 2 +- packages/cli/script/check-patches.ts | 33 -- packages/cli/src/cli.ts | 10 +- packages/cli/src/commands/api.ts | 2 +- packages/cli/src/commands/cli/feedback.ts | 2 +- packages/cli/src/commands/cli/setup.ts | 2 +- packages/cli/src/commands/init.ts | 2 +- packages/cli/src/commands/log/list.ts | 2 +- packages/cli/src/lib/agent-skills.ts | 2 +- packages/cli/src/lib/api/dashboards.ts | 2 +- packages/cli/src/lib/api/infrastructure.ts | 2 +- packages/cli/src/lib/api/logs.ts | 2 +- packages/cli/src/lib/api/teams.ts | 2 +- packages/cli/src/lib/auth-hint.ts | 2 +- packages/cli/src/lib/db/index.ts | 2 +- packages/cli/src/lib/delta-upgrade.ts | 2 +- packages/cli/src/lib/dsn/fs-utils.ts | 2 +- packages/cli/src/lib/error-reporting.ts | 2 +- packages/cli/src/lib/formatters/human.ts | 2 +- packages/cli/src/lib/hex-id-recovery.ts | 2 +- packages/cli/src/lib/init/interactive.ts | 2 +- .../lib/init/tools/create-sentry-project.ts | 2 +- .../cli/src/lib/init/tools/run-commands.ts | 2 +- packages/cli/src/lib/init/ui/ink-ui.ts | 2 +- packages/cli/src/lib/init/verify-setup.ts | 6 +- packages/cli/src/lib/init/wizard-runner.ts | 2 +- packages/cli/src/lib/interactive-login.ts | 2 +- packages/cli/src/lib/logger.ts | 28 +- packages/cli/src/lib/sdk-invoke.ts | 2 +- packages/cli/src/lib/sentry-client.ts | 2 +- packages/cli/src/lib/telemetry.ts | 91 +++++- .../cli/src/lib/telemetry/zstd-transport.ts | 12 +- packages/cli/src/lib/version-check.ts | 2 +- packages/cli/test/commands/api.test.ts | 2 +- packages/cli/test/commands/event/view.test.ts | 2 +- packages/cli/test/e2e/completion.test.ts | 2 +- packages/cli/test/e2e/local.test.ts | 2 +- packages/cli/test/fixtures/bench/helpers.ts | 2 +- .../cli/test/fixtures/local-agent-server.ts | 1 - .../cli/test/lib/api-client.coverage.test.ts | 2 +- .../test/lib/api-client.multiregion.test.ts | 2 +- packages/cli/test/lib/api/projects.test.ts | 2 +- packages/cli/test/lib/command.test.ts | 2 +- packages/cli/test/lib/dsn/fs-utils.test.ts | 2 +- .../cli/test/lib/dsn/project-root.test.ts | 2 +- packages/cli/test/lib/error-reporting.test.ts | 2 +- .../cli/test/lib/init/interactive.test.ts | 2 +- .../tools/run-commands-spawn.mocked.test.ts | 2 +- .../test/lib/init/ui/ink-ui-telemetry.test.ts | 2 +- .../init/verify-setup-windows.mocked.test.ts | 2 +- .../cli/test/lib/init/verify-setup.test.ts | 2 +- .../cli/test/lib/init/wizard-runner.test.ts | 2 +- .../cli/test/lib/run-commands.mocked.test.ts | 4 +- .../lib/scanner-flags.integration.test.ts | 2 +- .../lib/sdk-invoke-isolation.focused.test.ts | 2 +- .../test/lib/telemetry-credentials.test.ts | 11 +- .../cli/test/lib/telemetry-init-scope.test.ts | 24 ++ .../cli/test/lib/telemetry-session.test.ts | 8 +- packages/cli/test/lib/telemetry.test.ts | 14 +- ...-runner-handle-final-result.mocked.test.ts | 4 +- packages/cli/vitest.config.ts | 2 +- pnpm-lock.yaml | 78 +---- pnpm-workspace.yaml | 2 - 66 files changed, 208 insertions(+), 665 deletions(-) delete mode 100644 packages/cli/patches/@sentry%2Fcore@10.63.0.patch delete mode 100644 packages/cli/patches/@sentry%2Fnode-core@10.63.0.patch create mode 100644 packages/cli/test/lib/telemetry-init-scope.test.ts diff --git a/packages/cli/package.json b/packages/cli/package.json index ba3e9e9d1..1573155c3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -101,10 +101,9 @@ "@hono/node-server": "^2.0.10", "@mastra/client-js": "^1.38.0", "@sentry/api": "catalog:", - "@sentry/core": "10.63.0", + "@sentry/core": "catalog:", "@sentry/mcp-server": "workspace:*", - "@sentry/node": "10.65.0", - "@sentry/node-core": "10.63.0", + "@sentry/node": "catalog:", "@sentry/sqlish": "^1.0.1", "@sentry/symbolic": "13.7.0", "@sentry/toolkit-core": "workspace:*", diff --git a/packages/cli/patches/@sentry%2Fcore@10.63.0.patch b/packages/cli/patches/@sentry%2Fcore@10.63.0.patch deleted file mode 100644 index db315f63d..000000000 --- a/packages/cli/patches/@sentry%2Fcore@10.63.0.patch +++ /dev/null @@ -1,309 +0,0 @@ -diff --git a/build/cjs/index.js b/build/cjs/index.js -index e05216a98e34459736b64fa1d360878a42b8375d..124e25c91f683feb101f1373d468f030d689f4ed 100644 ---- a/build/cjs/index.js -+++ b/build/cjs/index.js -@@ -2,7 +2,6 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); - - const errors = require('./tracing/errors.js'); - const utils$2 = require('./tracing/utils.js'); --const idleSpan = require('./tracing/idleSpan.js'); - const sentrySpan = require('./tracing/sentrySpan.js'); - const sentryNonRecordingSpan = require('./tracing/sentryNonRecordingSpan.js'); - const spanstatus = require('./tracing/spanstatus.js'); -@@ -31,7 +30,6 @@ const base = require('./transports/base.js'); - const offline = require('./transports/offline.js'); - const multiplexed = require('./transports/multiplexed.js'); - const integration = require('./integration.js'); --const providerSkip = require('./utils/ai/providerSkip.js'); - const filterKeyValueData = require('./utils/data-collection/filterKeyValueData.js'); - const filterCookies = require('./utils/data-collection/filterCookies.js'); - const filterQueryParams = require('./utils/data-collection/filterQueryParams.js'); -@@ -63,21 +61,8 @@ const breadcrumbs = require('./breadcrumbs.js'); - const functiontostring = require('./integrations/functiontostring.js'); - const eventFilters = require('./integrations/eventFilters.js'); - const linkederrors = require('./integrations/linkederrors.js'); --const moduleMetadata = require('./integrations/moduleMetadata.js'); - const requestdata = require('./integrations/requestdata.js'); --const captureconsole = require('./integrations/captureconsole.js'); --const dedupe = require('./integrations/dedupe.js'); --const extraerrordata = require('./integrations/extraerrordata.js'); --const rewriteframes = require('./integrations/rewriteframes.js'); --const supabase = require('./integrations/supabase.js'); --const zoderrors = require('./integrations/zoderrors.js'); --const thirdPartyErrorsFilter = require('./integrations/third-party-errors-filter.js'); - const console$1 = require('./integrations/console.js'); --const featureFlagsIntegration = require('./integrations/featureFlags/featureFlagsIntegration.js'); --const growthbook = require('./integrations/featureFlags/growthbook.js'); --const conversationId = require('./integrations/conversationId.js'); --const profiling = require('./profiling.js'); --const fetch = require('./fetch.js'); - const feedback = require('./feedback.js'); - const internal = require('./logs/internal.js'); - const publicApi = require('./logs/public-api.js'); -@@ -85,26 +70,10 @@ const consoleIntegration = require('./logs/console-integration.js'); - const internal$1 = require('./metrics/internal.js'); - const publicApi$1 = require('./metrics/public-api.js'); - const consola = require('./integrations/consola.js'); --const index = require('./tracing/vercel-ai/index.js'); - const utils$3 = require('./tracing/ai/utils.js'); - const genAiAttributes = require('./tracing/ai/gen-ai-attributes.js'); --const utils = require('./tracing/vercel-ai/utils.js'); --const constants$7 = require('./tracing/vercel-ai/constants.js'); --const index$7 = require('./tracing/openai/index.js'); --const constants$6 = require('./tracing/openai/constants.js'); --const index$4 = require('./tracing/anthropic-ai/index.js'); --const constants = require('./tracing/anthropic-ai/constants.js'); --const index$6 = require('./tracing/google-genai/index.js'); --const constants$2 = require('./tracing/google-genai/constants.js'); --const index$1 = require('./tracing/langchain/index.js'); - const utils$1 = require('./tracing/langchain/utils.js'); --const constants$4 = require('./tracing/langchain/constants.js'); --const index$5 = require('./tracing/langgraph/index.js'); --const constants$5 = require('./tracing/langgraph/constants.js'); --const spanBuffer = require('./tracing/spans/spanBuffer.js'); --const hasSpanStreamingEnabled = require('./tracing/spans/hasSpanStreamingEnabled.js'); - const spanStreaming = require('./integrations/spanStreaming.js'); --const featureFlags = require('./utils/featureFlags.js'); - const aggregateErrors = require('./utils/aggregate-errors.js'); - const breadcrumbLogLevel = require('./utils/breadcrumb-log-level.js'); - const dsn = require('./utils/dsn.js'); -@@ -147,16 +116,12 @@ const metadata = require('./metadata.js'); - const escapeStringForRegex = require('./vendor/escapeStringForRegex.js'); - const randomSafeContext = require('./utils/randomSafeContext.js'); - const serverRuntimeClient = require('./server-runtime-client.js'); --const trpc = require('./trpc.js'); --const index$8 = require('./integrations/mcp-server/index.js'); - const node = require('./utils/node.js'); - const nodeStackTrace = require('./utils/node-stack-trace.js'); - const vercelWaitUntil = require('./utils/vercelWaitUntil.js'); - const flushIfServerless = require('./utils/flushIfServerless.js'); - const anr = require('./utils/anr.js'); - const timer = require('./utils/timer.js'); --const index$2 = require('./integrations/express/index.js'); --const postgresjs = require('./integrations/postgresjs.js'); - const sql = require('./utils/sql.js'); - const clientPatch = require('./integrations/http/client-patch.js'); - const clientSubscriptions = require('./integrations/http/client-subscriptions.js'); -@@ -166,7 +131,6 @@ const addOutgoingRequestBreadcrumb = require('./integrations/http/add-outgoing-r - const getRequestUrl = require('./integrations/http/get-request-url.js'); - const constants$3 = require('./integrations/http/constants.js'); - const browser = require('./utils/browser.js'); --const embeddings = require('./tracing/langchain/embeddings.js'); - - - -@@ -175,8 +139,6 @@ exports.getCapturedScopesOnSpan = utils$2.getCapturedScopesOnSpan; - exports.markSpanForOtelSourceInference = utils$2.markSpanForOtelSourceInference; - exports.setCapturedScopesOnSpan = utils$2.setCapturedScopesOnSpan; - exports.spanShouldInferOtelSource = utils$2.spanShouldInferOtelSource; --exports.TRACING_DEFAULTS = idleSpan.TRACING_DEFAULTS; --exports.startIdleSpan = idleSpan.startIdleSpan; - exports.SentrySpan = sentrySpan.SentrySpan; - exports.SentryNonRecordingSpan = sentryNonRecordingSpan.SentryNonRecordingSpan; - exports.SPAN_STATUS_ERROR = spanstatus.SPAN_STATUS_ERROR; -@@ -299,9 +261,6 @@ exports.defineIntegration = integration.defineIntegration; - exports.extendIntegration = integration.extendIntegration; - exports.getIntegrationsToSetup = integration.getIntegrationsToSetup; - exports.installedIntegrations = integration.installedIntegrations; --exports._INTERNAL_clearAiProviderSkips = providerSkip._INTERNAL_clearAiProviderSkips; --exports._INTERNAL_shouldSkipAiProviderWrapping = providerSkip._INTERNAL_shouldSkipAiProviderWrapping; --exports._INTERNAL_skipAiProviderWrapping = providerSkip._INTERNAL_skipAiProviderWrapping; - exports._INTERNAL_filterKeyValueData = filterKeyValueData.filterKeyValueData; - exports._INTERNAL_filterCookies = filterCookies.filterCookies; - exports._INTERNAL_filterQueryParams = filterQueryParams.filterQueryParams; -@@ -313,7 +272,6 @@ exports.prepareEvent = prepareEvent.prepareEvent; - exports.createCheckInEnvelope = checkin.createCheckInEnvelope; - exports.hasSpansEnabled = hasSpansEnabled.hasSpansEnabled; - exports.isStreamedBeforeSendSpanCallback = beforeSendSpan.isStreamedBeforeSendSpanCallback; --exports.withStreamedSpan = beforeSendSpan.withStreamedSpan; - exports.isSentryRequestUrl = isSentryRequestUrl.isSentryRequestUrl; - exports.handleCallbackErrors = handleCallbackErrors.handleCallbackErrors; - exports.fmt = parameterize.fmt; -@@ -363,23 +321,8 @@ exports.functionToStringIntegration = functiontostring.functionToStringIntegrati - exports.eventFiltersIntegration = eventFilters.eventFiltersIntegration; - exports.inboundFiltersIntegration = eventFilters.inboundFiltersIntegration; - exports.linkedErrorsIntegration = linkederrors.linkedErrorsIntegration; --exports.moduleMetadataIntegration = moduleMetadata.moduleMetadataIntegration; - exports.requestDataIntegration = requestdata.requestDataIntegration; --exports.captureConsoleIntegration = captureconsole.captureConsoleIntegration; --exports.dedupeIntegration = dedupe.dedupeIntegration; --exports.extraErrorDataIntegration = extraerrordata.extraErrorDataIntegration; --exports.rewriteFramesIntegration = rewriteframes.rewriteFramesIntegration; --exports.instrumentSupabaseClient = supabase.instrumentSupabaseClient; --exports.supabaseIntegration = supabase.supabaseIntegration; --exports.zodErrorsIntegration = zoderrors.zodErrorsIntegration; --exports.thirdPartyErrorFilterIntegration = thirdPartyErrorsFilter.thirdPartyErrorFilterIntegration; - exports.consoleIntegration = console$1.consoleIntegration; --exports.featureFlagsIntegration = featureFlagsIntegration.featureFlagsIntegration; --exports.growthbookIntegration = growthbook.growthbookIntegration; --exports.conversationIdIntegration = conversationId.conversationIdIntegration; --exports.profiler = profiling.profiler; --exports._INTERNAL_getTracingHeadersForFetchRequest = fetch._INTERNAL_getTracingHeadersForFetchRequest; --exports.instrumentFetchRequest = fetch.instrumentFetchRequest; - exports.captureFeedback = feedback.captureFeedback; - exports._INTERNAL_captureLog = internal._INTERNAL_captureLog; - exports._INTERNAL_captureSerializedLog = internal._INTERNAL_captureSerializedLog; -@@ -391,36 +334,12 @@ exports._INTERNAL_captureSerializedMetric = internal$1._INTERNAL_captureSerializ - exports._INTERNAL_flushMetricsBuffer = internal$1._INTERNAL_flushMetricsBuffer; - exports.metrics = publicApi$1; - exports.createConsolaReporter = consola.createConsolaReporter; --exports.addVercelAiProcessors = index.addVercelAiProcessors; --exports.getProviderMetadataAttributes = index.getProviderMetadataAttributes; - exports.getTruncatedJsonString = utils$3.getTruncatedJsonString; - exports.shouldEnableTruncation = utils$3.shouldEnableTruncation; - exports.GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE = genAiAttributes.GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE; - exports.GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE = genAiAttributes.GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE; --exports._INTERNAL_cleanupToolCallSpanContext = utils._INTERNAL_cleanupToolCallSpanContext; --exports._INTERNAL_getSpanContextForToolCallId = utils._INTERNAL_getSpanContextForToolCallId; --exports._INTERNAL_toolCallSpanContextMap = constants$7.toolCallSpanContextMap; --exports.instrumentOpenAiClient = index$7.instrumentOpenAiClient; --exports.OPENAI_INTEGRATION_NAME = constants$6.OPENAI_INTEGRATION_NAME; --exports.instrumentAnthropicAiClient = index$4.instrumentAnthropicAiClient; --exports.ANTHROPIC_AI_INTEGRATION_NAME = constants.ANTHROPIC_AI_INTEGRATION_NAME; --exports.instrumentGoogleGenAIClient = index$6.instrumentGoogleGenAIClient; --exports.GOOGLE_GENAI_INTEGRATION_NAME = constants$2.GOOGLE_GENAI_INTEGRATION_NAME; --exports.createLangChainCallbackHandler = index$1.createLangChainCallbackHandler; - exports._INTERNAL_mergeLangChainCallbackHandler = utils$1._INTERNAL_mergeLangChainCallbackHandler; --exports.LANGCHAIN_INTEGRATION_NAME = constants$4.LANGCHAIN_INTEGRATION_NAME; --exports.instrumentCreateReactAgent = index$5.instrumentCreateReactAgent; --exports.instrumentLangGraph = index$5.instrumentLangGraph; --exports.instrumentStateGraphCompile = index$5.instrumentStateGraphCompile; --exports.LANGGRAPH_INTEGRATION_NAME = constants$5.LANGGRAPH_INTEGRATION_NAME; --exports.SpanBuffer = spanBuffer.SpanBuffer; --exports.hasSpanStreamingEnabled = hasSpanStreamingEnabled.hasSpanStreamingEnabled; - exports.spanStreamingIntegration = spanStreaming.spanStreamingIntegration; --exports._INTERNAL_FLAG_BUFFER_SIZE = featureFlags._INTERNAL_FLAG_BUFFER_SIZE; --exports._INTERNAL_MAX_FLAGS_PER_SPAN = featureFlags._INTERNAL_MAX_FLAGS_PER_SPAN; --exports._INTERNAL_addFeatureFlagToActiveSpan = featureFlags._INTERNAL_addFeatureFlagToActiveSpan; --exports._INTERNAL_copyFlagsFromScopeToEvent = featureFlags._INTERNAL_copyFlagsFromScopeToEvent; --exports._INTERNAL_insertFlagToScope = featureFlags._INTERNAL_insertFlagToScope; - exports.applyAggregateErrorsToEvent = aggregateErrors.applyAggregateErrorsToEvent; - exports.getBreadcrumbLogLevelFromHttpStatusCode = breadcrumbLogLevel.getBreadcrumbLogLevelFromHttpStatusCode; - exports.dsnFromString = dsn.dsnFromString; -@@ -575,8 +494,6 @@ exports._INTERNAL_safeDateNow = randomSafeContext.safeDateNow; - exports._INTERNAL_safeMathRandom = randomSafeContext.safeMathRandom; - exports._INTERNAL_withRandomSafeContext = randomSafeContext.withRandomSafeContext; - exports.ServerRuntimeClient = serverRuntimeClient.ServerRuntimeClient; --exports.trpcMiddleware = trpc.trpcMiddleware; --exports.wrapMcpServerWithSentry = index$8.wrapMcpServerWithSentry; - exports.isNodeEnv = node.isNodeEnv; - exports.loadModule = node.loadModule; - exports.filenameIsInApp = nodeStackTrace.filenameIsInApp; -@@ -587,11 +504,6 @@ exports.flushIfServerless = flushIfServerless.flushIfServerless; - exports.callFrameToStackFrame = anr.callFrameToStackFrame; - exports.watchdogTimer = anr.watchdogTimer; - exports._INTERNAL_safeUnref = timer.safeUnref; --exports.expressErrorHandler = index$2.expressErrorHandler; --exports.patchExpressModule = index$2.patchExpressModule; --exports.setupExpressErrorHandler = index$2.setupExpressErrorHandler; --exports._INTERNAL_sanitizeSqlQuery = postgresjs._sanitizeSqlQuery; --exports.instrumentPostgresJsSql = postgresjs.instrumentPostgresJsSql; - exports._INTERNAL_getSqlQuerySummary = sql.getSqlQuerySummary; - exports.patchHttpModuleClient = clientPatch.patchHttpModuleClient; - exports.getHttpClientSubscriptions = clientSubscriptions.getHttpClientSubscriptions; -@@ -608,5 +520,4 @@ exports.HTTP_ON_SERVER_REQUEST = constants$3.HTTP_ON_SERVER_REQUEST; - exports.getComponentName = browser.getComponentName; - exports.getLocationHref = browser.getLocationHref; - exports.htmlTreeAsString = browser.htmlTreeAsString; --exports.instrumentLangChainEmbeddings = embeddings.instrumentLangChainEmbeddings; - //# sourceMappingURL=index.js.map -diff --git a/build/esm/index.js b/build/esm/index.js -index a90984714e9c91f844c0daa488caadbe20ceb493..dbca96e14eb70c535cd467a240f6990f6c6b0575 100644 ---- a/build/esm/index.js -+++ b/build/esm/index.js -@@ -1,6 +1,5 @@ - export { registerSpanErrorInstrumentation } from './tracing/errors.js'; - export { getCapturedScopesOnSpan, markSpanForOtelSourceInference, setCapturedScopesOnSpan, spanShouldInferOtelSource } from './tracing/utils.js'; --export { TRACING_DEFAULTS, startIdleSpan } from './tracing/idleSpan.js'; - export { SentrySpan } from './tracing/sentrySpan.js'; - export { SentryNonRecordingSpan } from './tracing/sentryNonRecordingSpan.js'; - export { SPAN_STATUS_ERROR, SPAN_STATUS_OK, SPAN_STATUS_UNSET, getSpanStatusFromHttpCode, setHttpStatus } from './tracing/spanstatus.js'; -@@ -29,7 +28,6 @@ export { createTransport } from './transports/base.js'; - export { makeOfflineTransport } from './transports/offline.js'; - export { MULTIPLEXED_TRANSPORT_EXTRA_KEY, makeMultiplexedTransport } from './transports/multiplexed.js'; - export { addIntegration, defineIntegration, extendIntegration, getIntegrationsToSetup, installedIntegrations } from './integration.js'; --export { _INTERNAL_clearAiProviderSkips, _INTERNAL_shouldSkipAiProviderWrapping, _INTERNAL_skipAiProviderWrapping } from './utils/ai/providerSkip.js'; - export { filterKeyValueData as _INTERNAL_filterKeyValueData } from './utils/data-collection/filterKeyValueData.js'; - export { filterCookies as _INTERNAL_filterCookies } from './utils/data-collection/filterCookies.js'; - export { filterQueryParams as _INTERNAL_filterQueryParams } from './utils/data-collection/filterQueryParams.js'; -@@ -38,7 +36,7 @@ export { applyScopeDataToEvent, getCombinedScopeData, mergeScopeData } from './u - export { prepareEvent } from './utils/prepareEvent.js'; - export { createCheckInEnvelope } from './checkin.js'; - export { hasSpansEnabled } from './utils/hasSpansEnabled.js'; --export { isStreamedBeforeSendSpanCallback, withStreamedSpan } from './tracing/spans/beforeSendSpan.js'; -+export { isStreamedBeforeSendSpanCallback } from './tracing/spans/beforeSendSpan.js'; - export { isSentryRequestUrl } from './utils/isSentryRequestUrl.js'; - export { handleCallbackErrors } from './utils/handleCallbackErrors.js'; - export { fmt, parameterize } from './utils/parameterize.js'; -@@ -61,21 +59,8 @@ export { addBreadcrumb } from './breadcrumbs.js'; - export { functionToStringIntegration } from './integrations/functiontostring.js'; - export { eventFiltersIntegration, inboundFiltersIntegration } from './integrations/eventFilters.js'; - export { linkedErrorsIntegration } from './integrations/linkederrors.js'; --export { moduleMetadataIntegration } from './integrations/moduleMetadata.js'; - export { requestDataIntegration } from './integrations/requestdata.js'; --export { captureConsoleIntegration } from './integrations/captureconsole.js'; --export { dedupeIntegration } from './integrations/dedupe.js'; --export { extraErrorDataIntegration } from './integrations/extraerrordata.js'; --export { rewriteFramesIntegration } from './integrations/rewriteframes.js'; --export { instrumentSupabaseClient, supabaseIntegration } from './integrations/supabase.js'; --export { zodErrorsIntegration } from './integrations/zoderrors.js'; --export { thirdPartyErrorFilterIntegration } from './integrations/third-party-errors-filter.js'; - export { consoleIntegration } from './integrations/console.js'; --export { featureFlagsIntegration } from './integrations/featureFlags/featureFlagsIntegration.js'; --export { growthbookIntegration } from './integrations/featureFlags/growthbook.js'; --export { conversationIdIntegration } from './integrations/conversationId.js'; --export { profiler } from './profiling.js'; --export { _INTERNAL_getTracingHeadersForFetchRequest, instrumentFetchRequest } from './fetch.js'; - export { captureFeedback } from './feedback.js'; - export { _INTERNAL_captureLog, _INTERNAL_captureSerializedLog, _INTERNAL_flushLogsBuffer } from './logs/internal.js'; - import * as publicApi from './logs/public-api.js'; -@@ -85,26 +70,10 @@ export { _INTERNAL_captureMetric, _INTERNAL_captureSerializedMetric, _INTERNAL_f - import * as publicApi$1 from './metrics/public-api.js'; - export { publicApi$1 as metrics }; - export { createConsolaReporter } from './integrations/consola.js'; --export { addVercelAiProcessors, getProviderMetadataAttributes } from './tracing/vercel-ai/index.js'; - export { getTruncatedJsonString, shouldEnableTruncation } from './tracing/ai/utils.js'; - export { GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE, GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE } from './tracing/ai/gen-ai-attributes.js'; --export { _INTERNAL_cleanupToolCallSpanContext, _INTERNAL_getSpanContextForToolCallId } from './tracing/vercel-ai/utils.js'; --export { toolCallSpanContextMap as _INTERNAL_toolCallSpanContextMap } from './tracing/vercel-ai/constants.js'; --export { instrumentOpenAiClient } from './tracing/openai/index.js'; --export { OPENAI_INTEGRATION_NAME } from './tracing/openai/constants.js'; --export { instrumentAnthropicAiClient } from './tracing/anthropic-ai/index.js'; --export { ANTHROPIC_AI_INTEGRATION_NAME } from './tracing/anthropic-ai/constants.js'; --export { instrumentGoogleGenAIClient } from './tracing/google-genai/index.js'; --export { GOOGLE_GENAI_INTEGRATION_NAME } from './tracing/google-genai/constants.js'; --export { createLangChainCallbackHandler } from './tracing/langchain/index.js'; - export { _INTERNAL_mergeLangChainCallbackHandler } from './tracing/langchain/utils.js'; --export { LANGCHAIN_INTEGRATION_NAME } from './tracing/langchain/constants.js'; --export { instrumentCreateReactAgent, instrumentLangGraph, instrumentStateGraphCompile } from './tracing/langgraph/index.js'; --export { LANGGRAPH_INTEGRATION_NAME } from './tracing/langgraph/constants.js'; --export { SpanBuffer } from './tracing/spans/spanBuffer.js'; --export { hasSpanStreamingEnabled } from './tracing/spans/hasSpanStreamingEnabled.js'; - export { spanStreamingIntegration } from './integrations/spanStreaming.js'; --export { _INTERNAL_FLAG_BUFFER_SIZE, _INTERNAL_MAX_FLAGS_PER_SPAN, _INTERNAL_addFeatureFlagToActiveSpan, _INTERNAL_copyFlagsFromScopeToEvent, _INTERNAL_insertFlagToScope } from './utils/featureFlags.js'; - export { applyAggregateErrorsToEvent } from './utils/aggregate-errors.js'; - export { getBreadcrumbLogLevelFromHttpStatusCode } from './utils/breadcrumb-log-level.js'; - export { dsnFromString, dsnToString, makeDsn } from './utils/dsn.js'; -@@ -147,16 +116,12 @@ export { getFilenameToMetadataMap } from './metadata.js'; - export { escapeStringForRegex } from './vendor/escapeStringForRegex.js'; - export { safeDateNow as _INTERNAL_safeDateNow, safeMathRandom as _INTERNAL_safeMathRandom, withRandomSafeContext as _INTERNAL_withRandomSafeContext } from './utils/randomSafeContext.js'; - export { ServerRuntimeClient } from './server-runtime-client.js'; --export { trpcMiddleware } from './trpc.js'; --export { wrapMcpServerWithSentry } from './integrations/mcp-server/index.js'; - export { isNodeEnv, loadModule } from './utils/node.js'; - export { filenameIsInApp, node, nodeStackLineParser } from './utils/node-stack-trace.js'; - export { vercelWaitUntil } from './utils/vercelWaitUntil.js'; - export { flushIfServerless } from './utils/flushIfServerless.js'; - export { callFrameToStackFrame, watchdogTimer } from './utils/anr.js'; - export { safeUnref as _INTERNAL_safeUnref } from './utils/timer.js'; --export { expressErrorHandler, patchExpressModule, setupExpressErrorHandler } from './integrations/express/index.js'; --export { _sanitizeSqlQuery as _INTERNAL_sanitizeSqlQuery, instrumentPostgresJsSql } from './integrations/postgresjs.js'; - export { getSqlQuerySummary as _INTERNAL_getSqlQuerySummary } from './utils/sql.js'; - export { patchHttpModuleClient } from './integrations/http/client-patch.js'; - export { getHttpClientSubscriptions } from './integrations/http/client-subscriptions.js'; -@@ -166,5 +131,4 @@ export { addOutgoingRequestBreadcrumb } from './integrations/http/add-outgoing-r - export { getRequestOptions, getRequestUrl, getRequestUrlFromClientRequest, getRequestUrlObject } from './integrations/http/get-request-url.js'; - export { HTTP_ON_CLIENT_REQUEST, HTTP_ON_SERVER_REQUEST } from './integrations/http/constants.js'; - export { getComponentName, getLocationHref, htmlTreeAsString } from './utils/browser.js'; --export { instrumentLangChainEmbeddings } from './tracing/langchain/embeddings.js'; - //# sourceMappingURL=index.js.map diff --git a/packages/cli/patches/@sentry%2Fnode-core@10.63.0.patch b/packages/cli/patches/@sentry%2Fnode-core@10.63.0.patch deleted file mode 100644 index 1ddb1e887..000000000 --- a/packages/cli/patches/@sentry%2Fnode-core@10.63.0.patch +++ /dev/null @@ -1,137 +0,0 @@ -diff --git a/build/cjs/light/index.js b/build/cjs/light/index.js -index 8fb4980846f783f7e352e7abd056bb95b8d86ea6..5514ac17921b832eb31c114e9bdf8999389e7060 100644 ---- a/build/cjs/light/index.js -+++ b/build/cjs/light/index.js -@@ -75,7 +75,6 @@ exports.addEventProcessor = core.addEventProcessor; - exports.addIntegration = core.addIntegration; - exports.bindScopeToEmitter = core.bindScopeToEmitter; - exports.captureCheckIn = core.captureCheckIn; --exports.captureConsoleIntegration = core.captureConsoleIntegration; - exports.captureEvent = core.captureEvent; - exports.captureException = core.captureException; - exports.captureFeedback = core.captureFeedback; -@@ -86,12 +85,9 @@ exports.consoleLoggingIntegration = core.consoleLoggingIntegration; - exports.continueTrace = core.continueTrace; - exports.createConsolaReporter = core.createConsolaReporter; - exports.createTransport = core.createTransport; --exports.dedupeIntegration = core.dedupeIntegration; - exports.endSession = core.endSession; - exports.envToBool = core.envToBool; - exports.eventFiltersIntegration = core.eventFiltersIntegration; --exports.extraErrorDataIntegration = core.extraErrorDataIntegration; --exports.featureFlagsIntegration = core.featureFlagsIntegration; - exports.flush = core.flush; - exports.functionToStringIntegration = core.functionToStringIntegration; - exports.getActiveSpan = core.getActiveSpan; -@@ -105,16 +101,13 @@ exports.getSpanDescendants = core.getSpanDescendants; - exports.getSpanStatusFromHttpCode = core.getSpanStatusFromHttpCode; - exports.getTraceData = core.getTraceData; - exports.getTraceMetaTags = core.getTraceMetaTags; --exports.instrumentSupabaseClient = core.instrumentSupabaseClient; - exports.isEnabled = core.isEnabled; - exports.isInitialized = core.isInitialized; - exports.lastEventId = core.lastEventId; - exports.linkedErrorsIntegration = core.linkedErrorsIntegration; - exports.metrics = core.metrics; - exports.parameterize = core.parameterize; --exports.profiler = core.profiler; - exports.requestDataIntegration = core.requestDataIntegration; --exports.rewriteFramesIntegration = core.rewriteFramesIntegration; - exports.setAttribute = core.setAttribute; - exports.setAttributes = core.setAttributes; - exports.setContext = core.setContext; -@@ -126,7 +119,6 @@ exports.setMeasurement = core.setMeasurement; - exports.setTag = core.setTag; - exports.setTags = core.setTags; - exports.setUser = core.setUser; --exports.spanStreamingIntegration = core.spanStreamingIntegration; - exports.spanToBaggageHeader = core.spanToBaggageHeader; - exports.spanToJSON = core.spanToJSON; - exports.spanToTraceHeader = core.spanToTraceHeader; -@@ -135,15 +127,10 @@ exports.startNewTrace = core.startNewTrace; - exports.startSession = core.startSession; - exports.startSpan = core.startSpan; - exports.startSpanManual = core.startSpanManual; --exports.supabaseIntegration = core.supabaseIntegration; - exports.suppressTracing = core.suppressTracing; --exports.trpcMiddleware = core.trpcMiddleware; - exports.updateSpanName = core.updateSpanName; - exports.withActiveSpan = core.withActiveSpan; - exports.withIsolationScope = core.withIsolationScope; - exports.withMonitor = core.withMonitor; - exports.withScope = core.withScope; --exports.withStreamedSpan = core.withStreamedSpan; --exports.wrapMcpServerWithSentry = core.wrapMcpServerWithSentry; --exports.zodErrorsIntegration = core.zodErrorsIntegration; - //# sourceMappingURL=index.js.map -diff --git a/build/cjs/light/sdk.js b/build/cjs/light/sdk.js -index 1e4c21c7825cb415e1e5b6371afc4af9afc90d7e..592979803554012e35ecf7dca50379744797a797 100644 ---- a/build/cjs/light/sdk.js -+++ b/build/cjs/light/sdk.js -@@ -22,6 +22,8 @@ const client = require('./client.js'); - const httpIntegration = require('./integrations/httpIntegration.js'); - const nativeNodeFetchIntegration = require('./integrations/nativeNodeFetchIntegration.js'); - -+// A repeated `init()` replaces the client, so the SIGTERM listener is registered once and flushes the latest client. -+let vercelSigtermClient; - function getDefaultIntegrations() { - return [ - // Common -@@ -81,9 +83,13 @@ function _init(_options = {}, getDefaultIntegrationsImpl) { - client$1.startClientReportTracking(); - updateScopeFromEnvVariables(); - if (process.env.VERCEL) { -- process.on("SIGTERM", async () => { -- await client$1.flush(200); -- }); -+ const isRegistered = !!vercelSigtermClient; -+ vercelSigtermClient = client$1; -+ if (!isRegistered) { -+ process.on("SIGTERM", async () => { -+ await vercelSigtermClient?.flush(200); -+ }); -+ } - } - return client$1; - } -diff --git a/build/esm/light/index.js b/build/esm/light/index.js -index f9e2a0818600f096bb980db7330338829256416d..a58d0aba64f228b9ced5291797507e8b1dadc111 100644 ---- a/build/esm/light/index.js -+++ b/build/esm/light/index.js -@@ -27,5 +27,5 @@ export { createMissingInstrumentationContext } from '../utils/createMissingInstr - export { makeNodeTransport } from '../transports/http.js'; - export { cron } from '../cron/index.js'; - export { NODE_VERSION } from '../nodeVersion.js'; --export { SDK_VERSION, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, Scope, addBreadcrumb, addEventProcessor, addIntegration, bindScopeToEmitter, captureCheckIn, captureConsoleIntegration, captureEvent, captureException, captureFeedback, captureMessage, captureSession, close, consoleLoggingIntegration, continueTrace, createConsolaReporter, createTransport, dedupeIntegration, endSession, envToBool, eventFiltersIntegration, extraErrorDataIntegration, featureFlagsIntegration, flush, functionToStringIntegration, getActiveSpan, getClient, getCurrentScope, getGlobalScope, getIsolationScope, getRequestUrl, getRootSpan, getSpanDescendants, getSpanStatusFromHttpCode, getTraceData, getTraceMetaTags, instrumentSupabaseClient, isEnabled, isInitialized, lastEventId, linkedErrorsIntegration, metrics, parameterize, profiler, requestDataIntegration, rewriteFramesIntegration, setAttribute, setAttributes, setContext, setCurrentClient, setExtra, setExtras, setHttpStatus, setMeasurement, setTag, setTags, setUser, spanStreamingIntegration, spanToBaggageHeader, spanToJSON, spanToTraceHeader, startInactiveSpan, startNewTrace, startSession, startSpan, startSpanManual, supabaseIntegration, suppressTracing, trpcMiddleware, updateSpanName, withActiveSpan, withIsolationScope, withMonitor, withScope, withStreamedSpan, wrapMcpServerWithSentry, zodErrorsIntegration } from '@sentry/core'; -+export { SDK_VERSION, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, Scope, addBreadcrumb, addEventProcessor, addIntegration, bindScopeToEmitter, captureCheckIn, captureEvent, captureException, captureFeedback, captureMessage, captureSession, close, consoleLoggingIntegration, continueTrace, createConsolaReporter, createTransport, endSession, envToBool, eventFiltersIntegration, flush, functionToStringIntegration, getActiveSpan, getClient, getCurrentScope, getGlobalScope, getIsolationScope, getRequestUrl, getRootSpan, getSpanDescendants, getSpanStatusFromHttpCode, getTraceData, getTraceMetaTags, isEnabled, isInitialized, lastEventId, linkedErrorsIntegration, metrics, parameterize, requestDataIntegration, setAttribute, setAttributes, setContext, setCurrentClient, setExtra, setExtras, setHttpStatus, setMeasurement, setTag, setTags, setUser, spanToBaggageHeader, spanToJSON, spanToTraceHeader, startInactiveSpan, startNewTrace, startSession, startSpan, startSpanManual, suppressTracing, updateSpanName, withActiveSpan, withIsolationScope, withMonitor, withScope } from '@sentry/core'; - //# sourceMappingURL=index.js.map -diff --git a/build/esm/light/sdk.js b/build/esm/light/sdk.js -index c8cef838dcc6bd8148681f68e7af8437e4db0987..ebb07284bb306b6a7484f3602ffa5042467ebace 100644 ---- a/build/esm/light/sdk.js -+++ b/build/esm/light/sdk.js -@@ -20,6 +20,8 @@ import { LightNodeClient } from './client.js'; - import { httpIntegration } from './integrations/httpIntegration.js'; - import { nativeNodeFetchIntegration } from './integrations/nativeNodeFetchIntegration.js'; - -+// A repeated `init()` replaces the client, so the SIGTERM listener is registered once and flushes the latest client. -+let vercelSigtermClient; - function getDefaultIntegrations() { - return [ - // Common -@@ -79,9 +81,13 @@ function _init(_options = {}, getDefaultIntegrationsImpl) { - client.startClientReportTracking(); - updateScopeFromEnvVariables(); - if (process.env.VERCEL) { -- process.on("SIGTERM", async () => { -- await client.flush(200); -- }); -+ const isRegistered = !!vercelSigtermClient; -+ vercelSigtermClient = client; -+ if (!isRegistered) { -+ process.on("SIGTERM", async () => { -+ await vercelSigtermClient?.flush(200); -+ }); -+ } - } - return client; - } diff --git a/packages/cli/script/bench.ts b/packages/cli/script/bench.ts index f17ff5708..e745c4890 100644 --- a/packages/cli/script/bench.ts +++ b/packages/cli/script/bench.ts @@ -320,7 +320,7 @@ type OpEntry = { async function buildOps(): Promise { // Lazy-import production code so unit tests can import the helpers/fixtures - // without loading all of @sentry/node-core. + // without loading all of @sentry/node. const { detectDsn, detectAllDsns } = await import("../src/lib/dsn/detector.js"); const { findProjectRoot } = await import("../src/lib/dsn/project-root.js"); diff --git a/packages/cli/script/check-patches.ts b/packages/cli/script/check-patches.ts index 627148c57..c263c0ba0 100644 --- a/packages/cli/script/check-patches.ts +++ b/packages/cli/script/check-patches.ts @@ -225,15 +225,6 @@ for (const [key, patchPath] of Object.entries(patches)) { * `-v`=version alias in `runApplication` so `-v` stays the Sentry CLI's * `--verbose` alias at every position; `--version` remains the version flag. * The stale marker is the original `inputs[0] === "-v"` version check. - * - * @sentry/core and @sentry/node-core: these are tree-shaking patches that strip - * unused re-exports (AI/integration modules) from the build barrels so esbuild - * excludes them from the bundle. They edit bundler-generated barrels and are - * therefore especially prone to silent context drift on a version bump. Each - * marker is a re-export the patch removes (present in the pristine package, - * absent once patched); its presence means the strip did NOT apply and the - * bundle will re-bloat with the AI integrations — re-introducing the dangling - * re-export class the patch guards against. */ const CONTENT_ASSERTIONS: ReadonlyArray<{ /** Installed file to inspect, relative to the resolved node_modules dir. */ @@ -308,30 +299,6 @@ const CONTENT_ASSERTIONS: ReadonlyArray<{ description: "@stricli/core CJS: pluggable `renderHelp` hook missing (`sentry --help --json` will fall back to text help instead of structured JSON)", }, - { - file: "@sentry/core/build/cjs/index.js", - staleMarker: "exports.instrumentOpenAiClient", - description: - "@sentry/core CJS: tree-shaking strip not applied (AI integrations re-bundled)", - }, - { - file: "@sentry/core/build/esm/index.js", - staleMarker: "from './tracing/openai/index.js'", - description: - "@sentry/core ESM: tree-shaking strip not applied (AI integrations re-bundled)", - }, - { - file: "@sentry/node-core/build/cjs/light/index.js", - staleMarker: "exports.dedupeIntegration", - description: - "@sentry/node-core CJS light: integration re-export strip not applied", - }, - { - file: "@sentry/node-core/build/esm/light/index.js", - staleMarker: "dedupeIntegration", - description: - "@sentry/node-core ESM light: integration re-export strip not applied", - }, ]; for (const assertion of CONTENT_ASSERTIONS) { diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 941da517e..3596639e8 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -2,7 +2,7 @@ * CLI runner with fast-path dispatch. * * Shell completion (`__complete`) is dispatched before any heavy imports - * to avoid loading `@sentry/node-core` (~280ms). All other commands go through + * to avoid loading `@sentry/node` (~280ms). All other commands go through * the full CLI with telemetry, middleware, and error recovery. * * Extracted from `bin.ts` so the logic is testable and reusable without @@ -26,7 +26,7 @@ async function preloadProjectContext(cwd: string): Promise { // Snapshot env-token host BEFORE anything mutates env.SENTRY_HOST/URL // (the .sentryclirc shim or the default-URL fallback below). Pins the // env-token's trust scope to the user's shell, not a repo-local file. - // Dynamic import: env-token-host chains into db/auth → telemetry → @sentry/node-core + // Dynamic import: env-token-host chains into db/auth → telemetry → @sentry/node const { captureEnvTokenHost } = await import("./lib/env-token-host.js"); captureEnvTokenHost(); @@ -70,12 +70,12 @@ async function preloadProjectContext(cwd: string): Promise { /** * Fast-path: shell completion. * - * Dispatched before importing the full CLI to avoid loading @sentry/node-core, + * Dispatched before importing the full CLI to avoid loading @sentry/node, * @stricli/core, and other heavy dependencies. Only loads the lightweight * completion engine and SQLite cache modules. */ export async function runCompletion(completionArgs: string[]): Promise { - // Disable telemetry so db/index.ts skips the @sentry/node-core lazy-require (~280ms) + // Disable telemetry so db/index.ts skips the @sentry/node lazy-require (~280ms) getEnv().SENTRY_CLI_NO_TELEMETRY = "1"; const { handleComplete } = await import("./lib/complete.js"); handleComplete(completionArgs); @@ -624,7 +624,7 @@ export async function runCli(cliArgs: string[]): Promise { * fuzzy matches and extracts org/project context from the raw argv. */ async function reportUnknownCommand(argv: string[]): Promise { - const Sentry = await import("@sentry/node-core/light"); + const Sentry = await import("@sentry/node"); const { resolveCommandPath } = await import("./lib/introspect.js"); const { routes } = await import("./app.js"); const { getDefaultOrganization } = await import("./lib/db/defaults.js"); diff --git a/packages/cli/src/commands/api.ts b/packages/cli/src/commands/api.ts index b00846361..aec7b19a6 100644 --- a/packages/cli/src/commands/api.ts +++ b/packages/cli/src/commands/api.ts @@ -7,7 +7,7 @@ import { access, readFile } from "node:fs/promises"; // oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; import type { SentryContext } from "../context.js"; import { appendSearchParams, rawApiRequest } from "../lib/api-client.js"; import { buildCommand } from "../lib/command.js"; diff --git a/packages/cli/src/commands/cli/feedback.ts b/packages/cli/src/commands/cli/feedback.ts index 2d73780a2..da547149a 100644 --- a/packages/cli/src/commands/cli/feedback.ts +++ b/packages/cli/src/commands/cli/feedback.ts @@ -10,7 +10,7 @@ import { isatty } from "node:tty"; // oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; import type { SentryContext } from "../../context.js"; import { buildCommand } from "../../lib/command.js"; import { ConfigError, ValidationError } from "../../lib/errors.js"; diff --git a/packages/cli/src/commands/cli/setup.ts b/packages/cli/src/commands/cli/setup.ts index ca334b96a..879a4b8bd 100644 --- a/packages/cli/src/commands/cli/setup.ts +++ b/packages/cli/src/commands/cli/setup.ts @@ -10,7 +10,7 @@ import { existsSync, unlinkSync } from "node:fs"; import { chmod, copyFile, mkdir, rename, unlink } from "node:fs/promises"; import { dirname, join } from "node:path"; import { isatty } from "node:tty"; -import { captureException } from "@sentry/node-core/light"; +import { captureException } from "@sentry/node"; import type { SentryContext } from "../../context.js"; import { installAgentSkills } from "../../lib/agent-skills.js"; import { assertAutoLoginHostTrusted } from "../../lib/auto-auth.js"; diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 02b2a4fda..0b78f105c 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -18,7 +18,7 @@ */ import path from "node:path"; -import { setTag } from "@sentry/node-core/light"; +import { setTag } from "@sentry/node"; import type { SentryContext } from "../context.js"; import { looksLikePath, parseOrgProjectArg } from "../lib/arg-parsing.js"; import { buildCommand } from "../lib/command.js"; diff --git a/packages/cli/src/commands/log/list.ts b/packages/cli/src/commands/log/list.ts index 1735e7e81..0c5f6a5d6 100644 --- a/packages/cli/src/commands/log/list.ts +++ b/packages/cli/src/commands/log/list.ts @@ -7,7 +7,7 @@ */ // oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; import type { SentryContext } from "../../context.js"; import { type LogSortDirection, diff --git a/packages/cli/src/lib/agent-skills.ts b/packages/cli/src/lib/agent-skills.ts index 27f585ed3..63d968fc8 100644 --- a/packages/cli/src/lib/agent-skills.ts +++ b/packages/cli/src/lib/agent-skills.ts @@ -12,7 +12,7 @@ import { accessSync, constants, existsSync, mkdirSync } from "node:fs"; import { rename, rm, writeFile } from "node:fs/promises"; import { basename, dirname, join } from "node:path"; -import { captureException } from "@sentry/node-core/light"; +import { captureException } from "@sentry/node"; import { SKILL_FILES } from "../generated/skill-content.js"; /** Where skills are installed */ diff --git a/packages/cli/src/lib/api/dashboards.ts b/packages/cli/src/lib/api/dashboards.ts index 5e115434a..09ba5532f 100644 --- a/packages/cli/src/lib/api/dashboards.ts +++ b/packages/cli/src/lib/api/dashboards.ts @@ -12,7 +12,7 @@ import { updateOrganizationDashboard, } from "@sentry/api"; // oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; import { resolveEventsSort } from "@sentry/toolkit-core/events-sort"; import { array, safeParse } from "valibot"; diff --git a/packages/cli/src/lib/api/infrastructure.ts b/packages/cli/src/lib/api/infrastructure.ts index d3f122d13..c3573193d 100644 --- a/packages/cli/src/lib/api/infrastructure.ts +++ b/packages/cli/src/lib/api/infrastructure.ts @@ -21,7 +21,7 @@ import { paginate as paginateFrom, } from "@sentry/toolkit-core/pagination"; // oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; import { type GenericSchema, safeParse } from "valibot"; import { extractRequiredScopes } from "../api-scope.js"; diff --git a/packages/cli/src/lib/api/logs.ts b/packages/cli/src/lib/api/logs.ts index 2d192387e..e14e0d631 100644 --- a/packages/cli/src/lib/api/logs.ts +++ b/packages/cli/src/lib/api/logs.ts @@ -7,7 +7,7 @@ import { listOrganizationEvents } from "@sentry/api"; // oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; import { type GenericSchema, safeParse } from "valibot"; import { diff --git a/packages/cli/src/lib/api/teams.ts b/packages/cli/src/lib/api/teams.ts index 0c761f7b8..9f888176d 100644 --- a/packages/cli/src/lib/api/teams.ts +++ b/packages/cli/src/lib/api/teams.ts @@ -11,7 +11,7 @@ import { listProjectTeams as sdkListProjectTeams, } from "@sentry/api"; // oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; import type { SentryTeam } from "../../types/index.js"; diff --git a/packages/cli/src/lib/auth-hint.ts b/packages/cli/src/lib/auth-hint.ts index 87ca85afd..b1ce75df6 100644 --- a/packages/cli/src/lib/auth-hint.ts +++ b/packages/cli/src/lib/auth-hint.ts @@ -24,7 +24,7 @@ */ // oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; import { getActiveEnvVarName, getRawEnvToken, diff --git a/packages/cli/src/lib/db/index.ts b/packages/cli/src/lib/db/index.ts index be549fb1c..6eb9b0b50 100644 --- a/packages/cli/src/lib/db/index.ts +++ b/packages/cli/src/lib/db/index.ts @@ -224,7 +224,7 @@ export function getDatabase(): Database { } // Wrap with tracing proxy for automatic query instrumentation. - // Lazy-require telemetry to avoid top-level import of @sentry/node-core (~85ms). + // Lazy-require telemetry to avoid top-level import of @sentry/node (~85ms). // Shell completions set SENTRY_CLI_NO_TELEMETRY=1 to skip this entirely. if (getEnv().SENTRY_CLI_NO_TELEMETRY === "1") { db = rawDb; diff --git a/packages/cli/src/lib/delta-upgrade.ts b/packages/cli/src/lib/delta-upgrade.ts index d2586325d..7ac8fbe0c 100644 --- a/packages/cli/src/lib/delta-upgrade.ts +++ b/packages/cli/src/lib/delta-upgrade.ts @@ -2,7 +2,7 @@ import { join } from "node:path"; // oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; import { applyPatchChainInMemory, extractStableChain as binpatchExtractStableChain, diff --git a/packages/cli/src/lib/dsn/fs-utils.ts b/packages/cli/src/lib/dsn/fs-utils.ts index d41eeec87..9d568be99 100644 --- a/packages/cli/src/lib/dsn/fs-utils.ts +++ b/packages/cli/src/lib/dsn/fs-utils.ts @@ -6,7 +6,7 @@ import { stat } from "node:fs/promises"; // oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; /** * Check if an error is an expected file system error that should be silently ignored. diff --git a/packages/cli/src/lib/error-reporting.ts b/packages/cli/src/lib/error-reporting.ts index fc8be2699..dd59c52a7 100644 --- a/packages/cli/src/lib/error-reporting.ts +++ b/packages/cli/src/lib/error-reporting.ts @@ -26,7 +26,7 @@ */ // oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; import { AliasNotFoundError, ArgumentParseError, diff --git a/packages/cli/src/lib/formatters/human.ts b/packages/cli/src/lib/formatters/human.ts index 565594723..7f55c8cef 100644 --- a/packages/cli/src/lib/formatters/human.ts +++ b/packages/cli/src/lib/formatters/human.ts @@ -8,7 +8,7 @@ */ // oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; import prettyMs from "pretty-ms"; import type { DashboardDetail, diff --git a/packages/cli/src/lib/hex-id-recovery.ts b/packages/cli/src/lib/hex-id-recovery.ts index 3f78448d4..e6d7dadfe 100644 --- a/packages/cli/src/lib/hex-id-recovery.ts +++ b/packages/cli/src/lib/hex-id-recovery.ts @@ -24,7 +24,7 @@ * module only owns the recovery-specific control flow. */ -import { addBreadcrumb } from "@sentry/node-core/light"; +import { addBreadcrumb } from "@sentry/node"; import type { SpanListItem } from "../types/index.js"; import { listLogs, listSpans, queryEvents } from "./api-client.js"; diff --git a/packages/cli/src/lib/init/interactive.ts b/packages/cli/src/lib/init/interactive.ts index 1d3cad659..6c040de37 100644 --- a/packages/cli/src/lib/init/interactive.ts +++ b/packages/cli/src/lib/init/interactive.ts @@ -10,7 +10,7 @@ * `LoggingUI` (CI / npm fallback). */ -import { setTag } from "@sentry/node-core/light"; +import { setTag } from "@sentry/node"; import { WizardError } from "../errors.js"; import { abortIfCancelled, diff --git a/packages/cli/src/lib/init/tools/create-sentry-project.ts b/packages/cli/src/lib/init/tools/create-sentry-project.ts index acd0f6591..af78a27ce 100644 --- a/packages/cli/src/lib/init/tools/create-sentry-project.ts +++ b/packages/cli/src/lib/init/tools/create-sentry-project.ts @@ -7,7 +7,7 @@ * that auto-creates a personal team for eligible members. */ -import { captureException } from "@sentry/node-core/light"; +import { captureException } from "@sentry/node"; import { createProjectWithAutoTeam, createProjectWithDsn, diff --git a/packages/cli/src/lib/init/tools/run-commands.ts b/packages/cli/src/lib/init/tools/run-commands.ts index 64a688375..19d414579 100644 --- a/packages/cli/src/lib/init/tools/run-commands.ts +++ b/packages/cli/src/lib/init/tools/run-commands.ts @@ -1,5 +1,5 @@ import { spawn } from "node:child_process"; -import { addBreadcrumb } from "@sentry/node-core/light"; +import { addBreadcrumb } from "@sentry/node"; import { whichSync } from "../../which.js"; import { DEFAULT_COMMAND_TIMEOUT_MS } from "../constants.js"; import type { RunCommandsPayload, ToolResult } from "../types.js"; diff --git a/packages/cli/src/lib/init/ui/ink-ui.ts b/packages/cli/src/lib/init/ui/ink-ui.ts index 369041f83..62b14f778 100644 --- a/packages/cli/src/lib/init/ui/ink-ui.ts +++ b/packages/cli/src/lib/init/ui/ink-ui.ts @@ -54,7 +54,7 @@ import { ReadStream } from "node:tty"; const _require = createRequire(import.meta.url); -import { addBreadcrumb, setTag } from "@sentry/node-core/light"; +import { addBreadcrumb, setTag } from "@sentry/node"; import { FULL_BANNER_LINES } from "../../banner.js"; import { openBrowser } from "../../browser.js"; import { CLI_VERSION } from "../../constants.js"; diff --git a/packages/cli/src/lib/init/verify-setup.ts b/packages/cli/src/lib/init/verify-setup.ts index e77a4bd22..aad64bded 100644 --- a/packages/cli/src/lib/init/verify-setup.ts +++ b/packages/cli/src/lib/init/verify-setup.ts @@ -14,11 +14,7 @@ import { type ChildProcess, spawn, spawnSync } from "node:child_process"; import { resolve } from "node:path"; -import { - addBreadcrumb, - captureException, - setTag, -} from "@sentry/node-core/light"; +import { addBreadcrumb, captureException, setTag } from "@sentry/node"; import { createSpotlightBuffer } from "@spotlightjs/spotlight/sdk"; import { BUFFER_SIZE, shutdownServer } from "../../commands/local/run.js"; import { buildApp, tryListen } from "../../commands/local/server.js"; diff --git a/packages/cli/src/lib/init/wizard-runner.ts b/packages/cli/src/lib/init/wizard-runner.ts index 8ee7b9de2..bc162d794 100644 --- a/packages/cli/src/lib/init/wizard-runner.ts +++ b/packages/cli/src/lib/init/wizard-runner.ts @@ -19,7 +19,7 @@ import { captureException, getTraceData, setTag, -} from "@sentry/node-core/light"; +} from "@sentry/node"; import { formatAuthHeader } from "../auth-header.js"; import { formatBanner } from "../banner.js"; import { CLI_VERSION } from "../constants.js"; diff --git a/packages/cli/src/lib/interactive-login.ts b/packages/cli/src/lib/interactive-login.ts index cc5d0abb8..948f27000 100644 --- a/packages/cli/src/lib/interactive-login.ts +++ b/packages/cli/src/lib/interactive-login.ts @@ -6,7 +6,7 @@ */ // oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; import { openBrowser } from "./browser.js"; import { setupCopyKeyListener } from "./clipboard.js"; import { getAccountRow } from "./db/accounts.js"; diff --git a/packages/cli/src/lib/logger.ts b/packages/cli/src/lib/logger.ts index f9f8f409f..569b56c7b 100644 --- a/packages/cli/src/lib/logger.ts +++ b/packages/cli/src/lib/logger.ts @@ -10,8 +10,8 @@ * renders in corrected local time — consola's default renders UTC in the SEA * binaries this CLI ships. * 2. **Sentry.createConsolaReporter()** — auto-forwards all log messages to Sentry - * structured logs via `_INTERNAL_captureLog`. Requires `enableLogs: true` in - * `Sentry.init()` (already enabled in telemetry.ts). + * structured logs via `_INTERNAL_captureLog`. Library mode drops these logs + * in `beforeSendLog` (see telemetry.ts). * * ## Log levels * @@ -59,12 +59,10 @@ * @module */ -import { createRequire } from "node:module"; +import { createConsolaReporter } from "@sentry/node"; import type { ConsolaInstance } from "consola"; import { createConsola } from "consola"; -const _require = createRequire(import.meta.url); - import { getEnv } from "./env.js"; import { formatLogTime } from "./timezone.js"; @@ -277,22 +275,10 @@ export function attachSentryReporter(): void { return; } - // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. - try { - // Dynamic import to avoid pulling in Sentry at module load time. - // The reporter is exported from @sentry/node-core/light (via @sentry/node → @sentry/core). - const Sentry = _require("@sentry/node-core/light") as { - createConsolaReporter: (options?: Record) => { - log: (logObj: unknown) => void; - }; - }; - - const sentryReporter = Sentry.createConsolaReporter(); - logger.addReporter(sentryReporter); - sentryReporterAttached = true; - } catch { - // Sentry not available (e.g., telemetry disabled) — continue without reporter - } + // Imported statically: the bundler leaves a runtime `require()` unresolved, + // which would either fail or load a host application's `@sentry/node`. + logger.addReporter(createConsolaReporter()); + sentryReporterAttached = true; } /** diff --git a/packages/cli/src/lib/sdk-invoke.ts b/packages/cli/src/lib/sdk-invoke.ts index 898b6ec01..27354f065 100644 --- a/packages/cli/src/lib/sdk-invoke.ts +++ b/packages/cli/src/lib/sdk-invoke.ts @@ -204,7 +204,7 @@ const ANSI_RE = /\x1b\[[0-9;]*m/g; async function flushTelemetry(): Promise { // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { - const Sentry = await import("@sentry/node-core/light"); + const Sentry = await import("@sentry/node"); const client = Sentry.getClient(); if (client) { await client.flush(3000); diff --git a/packages/cli/src/lib/sentry-client.ts b/packages/cli/src/lib/sentry-client.ts index 9c6dd26c3..5b520bd13 100644 --- a/packages/cli/src/lib/sentry-client.ts +++ b/packages/cli/src/lib/sentry-client.ts @@ -10,7 +10,7 @@ import { setTimeout as sleepMs } from "node:timers/promises"; import type { SentryClient } from "@sentry/api"; -import { getTraceData } from "@sentry/node-core/light"; +import { getTraceData } from "@sentry/node"; import { computeInvalidationPrefixes } from "@sentry/toolkit-core/response-cache"; import { formatAuthHeader, diff --git a/packages/cli/src/lib/telemetry.ts b/packages/cli/src/lib/telemetry.ts index fa1ec7fe9..135b29d00 100644 --- a/packages/cli/src/lib/telemetry.ts +++ b/packages/cli/src/lib/telemetry.ts @@ -11,8 +11,9 @@ import { chmodSync, statSync } from "node:fs"; import { createRequire } from "node:module"; +import type { Integration } from "@sentry/core"; // oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; const _require = createRequire(import.meta.url); @@ -271,9 +272,7 @@ export async function withTelemetry( * * @internal Exported for testing */ -export function createBeforeExitHandler( - client: Sentry.LightNodeClient, -): () => void { +export function createBeforeExitHandler(client: Sentry.NodeClient): () => void { let isFlushing = false; return () => { if (isFlushing) { @@ -428,6 +427,55 @@ const LIBRARY_EXCLUDED_INTEGRATIONS = new Set([ "NodeRuntimeMetrics", // runtime metrics timer would keep host event loop alive ]); +/** + * The v10 `@sentry/node-core/light` default integration set, rebuilt for v11. + * + * v11's `Sentry.init()` adds dozens of framework, database, and AI + * auto-instrumentations that would bloat the bundle and, in library mode, + * instrument the host application. Outgoing HTTP spans stay off because + * the CLI creates its own `http.client` spans around API requests. + */ +function getCliDefaultIntegrations(): Integration[] { + return [ + Sentry.eventFiltersIntegration(), + Sentry.functionToStringIntegration(), + Sentry.linkedErrorsIntegration(), + Sentry.requestDataIntegration(), + Sentry.systemErrorIntegration(), + Sentry.consoleIntegration(), + Sentry.httpIntegration({ spans: false }), + Sentry.nativeNodeFetchIntegration({ spans: false }), + Sentry.onUncaughtExceptionIntegration(), + Sentry.onUnhandledRejectionIntegration(), + Sentry.contextLinesIntegration(), + Sentry.localVariablesIntegration(), + Sentry.nodeContextIntegration(), + Sentry.childProcessIntegration(), + Sentry.processSessionIntegration(), + Sentry.modulesIntegration(), + ]; +} + +/** + * v11 defaults that collect more than v10 did with `sendDefaultPii` unset. + * This is the v10 baseline from the v11 migration guide. + */ +const HEADER_DENYLIST = ["forwarded", "-ip", "remote-", "via", "-user"]; +const V10_DATA_COLLECTION: Sentry.NodeOptions["dataCollection"] = { + userInfo: false, + cookies: false, + httpHeaders: { + request: { deny: HEADER_DENYLIST }, + response: { deny: HEADER_DENYLIST }, + }, + httpBodies: [], + urlQueryParams: { deny: HEADER_DENYLIST }, + genAI: { inputs: false, outputs: false }, + databaseQueryData: false, + queues: false, + graphQL: { document: false, variables: false }, +}; + /** * Check whether `util.getSystemErrorMap` exists at setup time. * Bun does not implement this Node.js API, which the SDK's NodeSystemError @@ -524,7 +572,7 @@ function setAgentTags(info: AgentInfo): void { export function initSentry( enabled: boolean, options?: { libraryMode?: boolean }, -): Sentry.LightNodeClient | undefined { +): Sentry.NodeClient | undefined { const libraryMode = options?.libraryMode ?? false; const environment = getCliEnvironment(); @@ -588,9 +636,20 @@ export function initSentry( // close(0) removes listeners synchronously; we don't need to await the flush. Sentry.getClient()?.close(0); - const client = Sentry.init({ + const client = Sentry.initWithoutDefaultIntegrations({ dsn: SENTRY_CLI_DSN, enabled, + defaultIntegrations: getCliDefaultIntegrations(), + // Keep v10's transaction model: dashboards and alerts rely on scope tags + // on transactions, and beforeSendTransaction scrubs server_name. + traceLifecycle: "static", + dataCollection: V10_DATA_COLLECTION, + // v11 defaults this to true, which would mark sessions errored on + // informational captureMessage calls such as `unknown_command`. + attachStacktrace: false, + // The CLI ships as a bundle, so runtime module hooks can't instrument it; + // in library mode they would instrument the host application instead. + enableRuntimeChannelInjection: false, // Compress outgoing envelopes with zstd (level 3) instead of gzip — // smaller payloads, faster compress/decompress on both sides. // Automatic gzip fallback when running on Node < 22.15, where @@ -608,8 +667,7 @@ export function initSentry( // The zstd-transport reads `caCerts` and passes it as `ca:` to // `http.request()`, and the SDK's fallback `makeNodeTransport` does the same. transportOptions: { caCerts: getCustomCaCerts() }, - // Keep default integrations but filter out ones that add overhead without benefit. - // Important: Don't use defaultIntegrations: false as it may break debug ID support. + // Filter out default integrations that add overhead without benefit. // NodeSystemError is excluded on runtimes missing util.getSystemErrorMap (Bun) — CLI-K1. // Library mode uses the extended exclusion set to avoid polluting the host process. integrations: (defaults) => { @@ -643,10 +701,9 @@ export function initSentry( return filtered; }, environment, - // Enable Sentry structured logs for non-exception telemetry (e.g., unexpected input warnings). - // Disabled when telemetry is off or in library mode — the SDK registers - // beforeExit handlers for log flushing that keep the event loop alive. - enableLogs: enabled && !libraryMode, + // v11 removed `enableLogs` and always captures `Sentry.logger.*` calls. + // Library mode never sent logs in v10, so keep dropping them there. + ...(libraryMode && { beforeSendLog: () => null }), // Disable client reports when telemetry is off or in library mode — the SDK // registers a setInterval + beforeExit handler that keep the event loop alive. ...((libraryMode || !enabled) && { sendClientReports: false }), @@ -694,6 +751,14 @@ export function initSentry( }, }); + // v11 attaches the client to the current scope before switching to the + // AsyncLocalStorage context strategy. When init runs inside a still-pending + // pre-init span (as in the CLI's middleware chain), that scope is lost in + // the switch and every later capture finds no client. + if (client) { + Sentry.setCurrentClient(client); + } + // Always remove our own previous handler on re-init. if (currentBeforeExitHandler) { process.removeListener("beforeExit", currentBeforeExitHandler); @@ -704,7 +769,7 @@ export function initSentry( const isBun = typeof process.versions.bun !== "undefined"; const runtime = isBun ? "bun" : "node"; - // LightNodeClient hardcodes runtime to { name: 'node' }. Override it so + // NodeClient defaults runtime to { name: 'node' }. Override it so // events carry the correct runtime when running as a compiled Bun binary. const opts = client.getOptions(); opts.runtime = { diff --git a/packages/cli/src/lib/telemetry/zstd-transport.ts b/packages/cli/src/lib/telemetry/zstd-transport.ts index f78cb5ebd..9452b937c 100644 --- a/packages/cli/src/lib/telemetry/zstd-transport.ts +++ b/packages/cli/src/lib/telemetry/zstd-transport.ts @@ -12,7 +12,7 @@ * when the transport is created, every envelope uses zstd; otherwise * every envelope uses gzip. * - * This mirrors `@sentry/node-core/transports/http.js` `makeNodeTransport` + * This mirrors `@sentry/node/transports/http.js` `makeNodeTransport` * — URL parsing, `no_proxy` handling, proxy agent, CA certs, keepAlive, * IPv6 hostname unwrapping, rate-limit response header normalization — * but swaps out the gzip-via-stream-pipe for an async one-shot compress @@ -45,10 +45,10 @@ import { type TransportRequest, type TransportRequestExecutor, } from "@sentry/core"; -import { - makeNodeTransport, - type NodeTransportOptions, -} from "@sentry/node-core/light"; +import { makeNodeTransport } from "@sentry/node"; + +/** `@sentry/node` v11 no longer re-exports this type from its entry point. */ +type NodeTransportOptions = Parameters[0]; /** Codec actually applied to a given envelope. */ type AppliedEncoding = "zstd" | "gzip" | "none"; @@ -155,7 +155,7 @@ export function makeCompressedTransport( * no_proxy. When true, the caller falls back to the SDK's default * transport (which handles CONNECT tunneling). * - * Mirrors `@sentry/node-core/transports/http.js` `applyNoProxyOption`'s + * Mirrors `@sentry/node/transports/http.js` `applyNoProxyOption`'s * proxy-resolution priority: * - http → `options.proxy` | `http_proxy` * - https → `options.proxy` | `https_proxy` | `http_proxy` diff --git a/packages/cli/src/lib/version-check.ts b/packages/cli/src/lib/version-check.ts index f574380ce..d3dd708d4 100644 --- a/packages/cli/src/lib/version-check.ts +++ b/packages/cli/src/lib/version-check.ts @@ -8,7 +8,7 @@ */ // oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; import { compare as semverCompare } from "semver"; import type { UpgradeSource } from "./binary.js"; import { CLI_VERSION } from "./constants.js"; diff --git a/packages/cli/test/commands/api.test.ts b/packages/cli/test/commands/api.test.ts index 80f8c8649..6f90ee7a7 100644 --- a/packages/cli/test/commands/api.test.ts +++ b/packages/cli/test/commands/api.test.ts @@ -8,7 +8,7 @@ import { writeFile } from "node:fs/promises"; import { Readable } from "node:stream"; // oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { apiCommand, diff --git a/packages/cli/test/commands/event/view.test.ts b/packages/cli/test/commands/event/view.test.ts index ae41ad7d5..f64dbd4c2 100644 --- a/packages/cli/test/commands/event/view.test.ts +++ b/packages/cli/test/commands/event/view.test.ts @@ -6,7 +6,7 @@ */ // oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { collectEventIds, diff --git a/packages/cli/test/e2e/completion.test.ts b/packages/cli/test/e2e/completion.test.ts index 68e51d6c3..8c67485dc 100644 --- a/packages/cli/test/e2e/completion.test.ts +++ b/packages/cli/test/e2e/completion.test.ts @@ -2,7 +2,7 @@ * Completion Latency E2E Tests * * Spawns the actual CLI binary with `__complete` args and measures - * wall-clock latency. The completion fast-path skips @sentry/node-core, + * wall-clock latency. The completion fast-path skips @sentry/node, * Stricli, and middleware — these tests catch regressions that would * re-introduce heavy imports into the completion path. * diff --git a/packages/cli/test/e2e/local.test.ts b/packages/cli/test/e2e/local.test.ts index f28042ec6..baa9dc7fb 100644 --- a/packages/cli/test/e2e/local.test.ts +++ b/packages/cli/test/e2e/local.test.ts @@ -747,7 +747,7 @@ describe("Local daemon E2E", () => { "--input-type=module", "-e", `import * as Sentry from '@sentry/node'; - Sentry.init({enableLogs: true, tracesSampleRate: 1}); + Sentry.init({tracesSampleRate: 1}); const id = Sentry.captureException(new Error('Local real SDK event')); console.error('SDK_EVENT:' + id); await Sentry.flush(2000); diff --git a/packages/cli/test/fixtures/bench/helpers.ts b/packages/cli/test/fixtures/bench/helpers.ts index cd9f486de..6ddbcf9d9 100644 --- a/packages/cli/test/fixtures/bench/helpers.ts +++ b/packages/cli/test/fixtures/bench/helpers.ts @@ -149,7 +149,7 @@ export async function withBenchDb( fn: (configDir: string) => Promise, ): Promise { // Import lazily — importing `src/lib/db/index.js` has non-trivial side - // effects (loads bun:sqlite, @sentry/node-core via createTracedDatabase). + // effects (loads bun:sqlite, @sentry/node via createTracedDatabase). const { closeDatabase } = await import("../../../src/lib/db/index.js"); const saved = process.env.SENTRY_CONFIG_DIR; diff --git a/packages/cli/test/fixtures/local-agent-server.ts b/packages/cli/test/fixtures/local-agent-server.ts index 0970c5dd2..4cb6724b1 100644 --- a/packages/cli/test/fixtures/local-agent-server.ts +++ b/packages/cli/test/fixtures/local-agent-server.ts @@ -105,7 +105,6 @@ export function createLocalAgentServer() { function startServer(): void { init({ dsn: process.env.SENTRY_DSN, - enableLogs: true, tracesSampleRate: 1, }); diff --git a/packages/cli/test/lib/api-client.coverage.test.ts b/packages/cli/test/lib/api-client.coverage.test.ts index a37b63eaf..feecfebdc 100644 --- a/packages/cli/test/lib/api-client.coverage.test.ts +++ b/packages/cli/test/lib/api-client.coverage.test.ts @@ -7,7 +7,7 @@ */ // oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; import { number, object, string } from "valibot"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { resolveEventInOrg } from "../../src/lib/api/events.js"; diff --git a/packages/cli/test/lib/api-client.multiregion.test.ts b/packages/cli/test/lib/api-client.multiregion.test.ts index 959df45be..85487c8d1 100644 --- a/packages/cli/test/lib/api-client.multiregion.test.ts +++ b/packages/cli/test/lib/api-client.multiregion.test.ts @@ -6,7 +6,7 @@ */ // oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { findProjectByDsnKey, diff --git a/packages/cli/test/lib/api/projects.test.ts b/packages/cli/test/lib/api/projects.test.ts index 4d33f57a2..92da3bec3 100644 --- a/packages/cli/test/lib/api/projects.test.ts +++ b/packages/cli/test/lib/api/projects.test.ts @@ -8,7 +8,7 @@ */ // oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { createProjectWithDsn, diff --git a/packages/cli/test/lib/command.test.ts b/packages/cli/test/lib/command.test.ts index 7283321fb..d85cb039d 100644 --- a/packages/cli/test/lib/command.test.ts +++ b/packages/cli/test/lib/command.test.ts @@ -8,7 +8,7 @@ import { setTimeout as sleep } from "node:timers/promises"; // oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; import { buildApplication, type CommandContext, diff --git a/packages/cli/test/lib/dsn/fs-utils.test.ts b/packages/cli/test/lib/dsn/fs-utils.test.ts index 03043662b..2f886c535 100644 --- a/packages/cli/test/lib/dsn/fs-utils.test.ts +++ b/packages/cli/test/lib/dsn/fs-utils.test.ts @@ -11,7 +11,7 @@ const { captureException } = vi.hoisted(() => ({ captureException: vi.fn(), })); -vi.mock("@sentry/node-core/light", () => ({ +vi.mock("@sentry/node", () => ({ captureException, startSpan: (_opts: unknown, fn: () => unknown) => fn(), })); diff --git a/packages/cli/test/lib/dsn/project-root.test.ts b/packages/cli/test/lib/dsn/project-root.test.ts index 26c1e6faf..dbcdc4ad2 100644 --- a/packages/cli/test/lib/dsn/project-root.test.ts +++ b/packages/cli/test/lib/dsn/project-root.test.ts @@ -20,7 +20,7 @@ const { noopSpan } = vi.hoisted(() => ({ }, })); -vi.mock("@sentry/node-core/light", () => ({ +vi.mock("@sentry/node", () => ({ startSpan: (_opts: unknown, fn: (span: unknown) => unknown) => fn(noopSpan), captureException: vi.fn(), })); diff --git a/packages/cli/test/lib/error-reporting.test.ts b/packages/cli/test/lib/error-reporting.test.ts index a673192e7..d2eafb738 100644 --- a/packages/cli/test/lib/error-reporting.test.ts +++ b/packages/cli/test/lib/error-reporting.test.ts @@ -9,7 +9,7 @@ */ // oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; import { ArgumentParseError, FlagNotFoundError } from "@stricli/core"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { formatAuthHeader } from "../../src/lib/auth-header.js"; diff --git a/packages/cli/test/lib/init/interactive.test.ts b/packages/cli/test/lib/init/interactive.test.ts index 3fce6e21d..6c1608649 100644 --- a/packages/cli/test/lib/init/interactive.test.ts +++ b/packages/cli/test/lib/init/interactive.test.ts @@ -8,7 +8,7 @@ */ // oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; import { afterEach, describe, expect, test, vi } from "vitest"; import { WizardError } from "../../../src/lib/errors.js"; import { handleInteractive } from "../../../src/lib/init/interactive.js"; diff --git a/packages/cli/test/lib/init/tools/run-commands-spawn.mocked.test.ts b/packages/cli/test/lib/init/tools/run-commands-spawn.mocked.test.ts index b4b021bdf..a99cf3714 100644 --- a/packages/cli/test/lib/init/tools/run-commands-spawn.mocked.test.ts +++ b/packages/cli/test/lib/init/tools/run-commands-spawn.mocked.test.ts @@ -47,7 +47,7 @@ vi.mock("node:child_process", async () => { }; }); -vi.mock("@sentry/node-core/light", () => ({ +vi.mock("@sentry/node", () => ({ addBreadcrumb: vi.fn(), })); diff --git a/packages/cli/test/lib/init/ui/ink-ui-telemetry.test.ts b/packages/cli/test/lib/init/ui/ink-ui-telemetry.test.ts index b0df7ede3..3ae550c96 100644 --- a/packages/cli/test/lib/init/ui/ink-ui-telemetry.test.ts +++ b/packages/cli/test/lib/init/ui/ink-ui-telemetry.test.ts @@ -1,5 +1,5 @@ // oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; import { afterEach, describe, expect, test, vi } from "vitest"; import { InkUI } from "../../../../src/lib/init/ui/ink-ui.js"; import { diff --git a/packages/cli/test/lib/init/verify-setup-windows.mocked.test.ts b/packages/cli/test/lib/init/verify-setup-windows.mocked.test.ts index 7c333270c..cc181fecb 100644 --- a/packages/cli/test/lib/init/verify-setup-windows.mocked.test.ts +++ b/packages/cli/test/lib/init/verify-setup-windows.mocked.test.ts @@ -69,7 +69,7 @@ vi.mock("node:child_process", async (importOriginal) => { }; }); -vi.mock("@sentry/node-core/light", () => ({ +vi.mock("@sentry/node", () => ({ addBreadcrumb: vi.fn(), captureException: vi.fn(), setTag: vi.fn(), diff --git a/packages/cli/test/lib/init/verify-setup.test.ts b/packages/cli/test/lib/init/verify-setup.test.ts index a4d76b7bc..8dc0d200e 100644 --- a/packages/cli/test/lib/init/verify-setup.test.ts +++ b/packages/cli/test/lib/init/verify-setup.test.ts @@ -11,7 +11,7 @@ const sentryMocks = vi.hoisted(() => ({ setTag: vi.fn(), })); -vi.mock("@sentry/node-core/light", () => sentryMocks); +vi.mock("@sentry/node", () => sentryMocks); type FixtureProcesses = { shellPid: number; diff --git a/packages/cli/test/lib/init/wizard-runner.test.ts b/packages/cli/test/lib/init/wizard-runner.test.ts index 1ae9fff1d..57c0ca784 100644 --- a/packages/cli/test/lib/init/wizard-runner.test.ts +++ b/packages/cli/test/lib/init/wizard-runner.test.ts @@ -1,6 +1,6 @@ import { MastraClient } from "@mastra/client-js"; // oxlint-disable-next-line sentry-cli/no-namespace-import -- spyOn requires object reference -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; import { afterEach, beforeEach, diff --git a/packages/cli/test/lib/run-commands.mocked.test.ts b/packages/cli/test/lib/run-commands.mocked.test.ts index bc17c5411..5d025e682 100644 --- a/packages/cli/test/lib/run-commands.mocked.test.ts +++ b/packages/cli/test/lib/run-commands.mocked.test.ts @@ -1,7 +1,7 @@ /** * Unit tests for the run-commands tool. * - * Kept as a mocked sibling file because vi.mock() on @sentry/node-core/light + * Kept as a mocked sibling file because vi.mock() on @sentry/node * must precede all module imports to take effect. */ @@ -22,7 +22,7 @@ const { breadcrumbs } = vi.hoisted(() => ({ breadcrumbs: [] as Breadcrumb[], })); -vi.mock("@sentry/node-core/light", () => ({ +vi.mock("@sentry/node", () => ({ addBreadcrumb: (crumb: Breadcrumb) => breadcrumbs.push(crumb), })); diff --git a/packages/cli/test/lib/scanner-flags.integration.test.ts b/packages/cli/test/lib/scanner-flags.integration.test.ts index 885ad9c64..707355494 100644 --- a/packages/cli/test/lib/scanner-flags.integration.test.ts +++ b/packages/cli/test/lib/scanner-flags.integration.test.ts @@ -21,7 +21,7 @@ import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; // oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; import { ExitCode, run } from "@stricli/core"; import { describe, expect, test, vi } from "vitest"; import { app } from "../../src/app.js"; diff --git a/packages/cli/test/lib/sdk-invoke-isolation.focused.test.ts b/packages/cli/test/lib/sdk-invoke-isolation.focused.test.ts index 744c1a73c..5e9a1ae6e 100644 --- a/packages/cli/test/lib/sdk-invoke-isolation.focused.test.ts +++ b/packages/cli/test/lib/sdk-invoke-isolation.focused.test.ts @@ -42,7 +42,7 @@ vi.mock("../../src/lib/telemetry.js", () => ({ callback: (span: undefined) => Promise, ): Promise => await callback(undefined), })); -vi.mock("@sentry/node-core/light", () => ({ getClient: () => null })); +vi.mock("@sentry/node", () => ({ getClient: () => null })); import { getCustomHeaders } from "../../src/lib/custom-headers.js"; import { getEnv } from "../../src/lib/env.js"; diff --git a/packages/cli/test/lib/telemetry-credentials.test.ts b/packages/cli/test/lib/telemetry-credentials.test.ts index 1c69dc872..21d4082aa 100644 --- a/packages/cli/test/lib/telemetry-credentials.test.ts +++ b/packages/cli/test/lib/telemetry-credentials.test.ts @@ -13,7 +13,7 @@ import { setContext, startSpan, withScope, -} from "@sentry/node-core/light"; +} from "@sentry/node"; import { afterEach, describe, expect, test, vi } from "vitest"; import { extractMessagePrefix } from "../../src/lib/error-reporting.js"; import { redactTelemetryEnvelope } from "../../src/lib/telemetry/credential-redaction.js"; @@ -39,7 +39,8 @@ function captureTelemetry() { } // Enable capture only after every delivery route is intercepted. client.getOptions().enabled = true; - client.getOptions().enableLogs = true; + // Library mode drops logs; lift that so log scrubbing is exercised too. + client.getOptions().beforeSendLog = undefined; client.init(); function serializedEnvelope(type: string): string { const envelope = send.mock.calls.find(([entry]) => @@ -55,8 +56,10 @@ function captureTelemetry() { afterEach(async () => { await client?.close(0); - getCurrentScope().clear(); - getIsolationScope().clear(); + // v11 removed Scope.clear(); reset only the state these tests set. + for (const scope of [getCurrentScope(), getIsolationScope()]) { + scope.setContext("synthetic", null).clearBreadcrumbs(); + } vi.restoreAllMocks(); }); diff --git a/packages/cli/test/lib/telemetry-init-scope.test.ts b/packages/cli/test/lib/telemetry-init-scope.test.ts new file mode 100644 index 000000000..ca7599f5c --- /dev/null +++ b/packages/cli/test/lib/telemetry-init-scope.test.ts @@ -0,0 +1,24 @@ +/** + * Runs in its own file so the SDK starts on its default stack-based context + * strategy, which is what the CLI sees before the first `initSentry()`. + */ + +// oxlint-disable-next-line sentry-cli/no-namespace-import -- inspect the SDK's active client +import * as Sentry from "@sentry/node"; +import { afterEach, expect, test } from "vitest"; +import { initSentry } from "../../src/lib/telemetry.js"; + +afterEach(async () => { + await Sentry.getClient()?.close(0); +}); + +test("binds the client when init runs inside a pending pre-init span", async () => { + let client: ReturnType; + await Sentry.startSpan({ name: "pre-init" }, async () => { + await Promise.resolve(); + client = initSentry(true, { libraryMode: true }); + }); + + expect(client).toBeDefined(); + expect(Sentry.getClient()).toBe(client); +}); diff --git a/packages/cli/test/lib/telemetry-session.test.ts b/packages/cli/test/lib/telemetry-session.test.ts index 36f146586..536d7dd1d 100644 --- a/packages/cli/test/lib/telemetry-session.test.ts +++ b/packages/cli/test/lib/telemetry-session.test.ts @@ -12,7 +12,7 @@ */ // oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; import { afterEach, describe, expect, test, vi } from "vitest"; import { createBeforeExitHandler, @@ -21,13 +21,13 @@ import { describe("createBeforeExitHandler", () => { /** - * Create a minimal mock LightNodeClient for testing the beforeExit handler. + * Create a minimal mock NodeClient for testing the beforeExit handler. * Only needs flush() since that's all the handler uses from the client. */ - function createMockClient(): Sentry.LightNodeClient { + function createMockClient(): Sentry.NodeClient { return { flush: () => Promise.resolve(true), - } as unknown as Sentry.LightNodeClient; + } as unknown as Sentry.NodeClient; } test("ends OK session on beforeExit", () => { diff --git a/packages/cli/test/lib/telemetry.test.ts b/packages/cli/test/lib/telemetry.test.ts index 2b61fd76b..2bfdd6a43 100644 --- a/packages/cli/test/lib/telemetry.test.ts +++ b/packages/cli/test/lib/telemetry.test.ts @@ -7,7 +7,7 @@ import { chmodSync, mkdirSync, rmSync } from "node:fs"; import { setTimeout as sleep } from "node:timers/promises"; // oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking -import * as Sentry from "@sentry/node-core/light"; +import * as Sentry from "@sentry/node"; import { afterAll, afterEach, @@ -108,6 +108,18 @@ describe("initSentry", () => { expect(client?.getOptions().release).toBe("0.0.0-dev"); }); + test("library mode installs only error-capture integrations", () => { + const client = initSentry(true, { libraryMode: true }); + const names = client?.getOptions().integrations.map((i) => i.name); + expect(names?.sort()).toEqual([ + "EventFilters", + "LinkedErrors", + "LocalVariablesAsync", + "NodeSystemError", + "RequestData", + ]); + }); + describe("under VERCEL", () => { let originalVercel: string | undefined; let preTestSigterm: Set; diff --git a/packages/cli/test/lib/wizard-runner-handle-final-result.mocked.test.ts b/packages/cli/test/lib/wizard-runner-handle-final-result.mocked.test.ts index 4580f166c..02fb89059 100644 --- a/packages/cli/test/lib/wizard-runner-handle-final-result.mocked.test.ts +++ b/packages/cli/test/lib/wizard-runner-handle-final-result.mocked.test.ts @@ -1,7 +1,7 @@ /** * Unit tests for handleFinalResult in wizard-runner.ts. * - * Kept as a mocked sibling file because vi.mock() on @sentry/node-core/light + * Kept as a mocked sibling file because vi.mock() on @sentry/node * would pollute the module graph for other wizard-runner tests that may not * want Sentry calls mocked. */ @@ -20,7 +20,7 @@ const { tags } = vi.hoisted(() => ({ tags: {} as Record, })); -vi.mock("@sentry/node-core/light", () => ({ +vi.mock("@sentry/node", () => ({ addBreadcrumb: () => null, captureException: () => null, getTraceData: () => ({}), diff --git a/packages/cli/vitest.config.ts b/packages/cli/vitest.config.ts index e68d88448..0462145d1 100644 --- a/packages/cli/vitest.config.ts +++ b/packages/cli/vitest.config.ts @@ -96,7 +96,7 @@ export default defineConfig({ // vi.spyOn. Without inlining, ESM namespace objects are frozen // and spyOn throws "Cannot redefine property". inline: [ - "@sentry/node-core", + "@sentry/node", "@sentry/core", "@clack/prompts", "node:child_process", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 691c5ef46..e0cc31f2b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -203,8 +203,6 @@ packageExtensionsChecksum: sha256-PIoL2jn0sFpFHgcMKW97dNeqBXVNxH6qFIzsWIBFOMY= patchedDependencies: '@ai-sdk/provider-utils@2.2.8': 4649a74595006c4a262d453549e091db331ade06b4f4320957af1b2c659b8ee3 '@cloudflare/workers-oauth-provider@0.10.3': 34910b67929c1888b1492384c514d6baeedc4a439f66e21375dee5af404834f8 - '@sentry/core@10.63.0': e663994979ff877a26ab6d4dea5968fbaee4ccdfdeb85623983535a572678940 - '@sentry/node-core@10.63.0': a537ad4fb79b41d54b6294108829d470d10a45d3895041f16ab11168de0f1983 '@stricli/core@1.2.8': cbf047581d38be04108d3921321c14a778272c24004da8110d4498d4f2a6b2e1 importers: @@ -418,7 +416,7 @@ importers: devDependencies: '@spotlightjs/spotlight': specifier: ^4.11.7 - version: 4.11.8(@cfworker/json-schema@4.1.1)(hono-rate-limiter@0.4.2(hono@4.13.8)) + version: 4.11.8(@cfworker/json-schema@4.1.1)(@opentelemetry/core@2.11.0(@opentelemetry/api@1.9.1))(hono-rate-limiter@0.4.2(hono@4.13.8)) '@testing-library/react': specifier: ^16.3.2 version: 16.3.3(@testing-library/dom@10.4.2)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) @@ -471,17 +469,14 @@ importers: specifier: 'catalog:' version: 0.400.0(valibot@1.4.2(typescript@5.9.3))(zod@4.6.7) '@sentry/core': - specifier: 10.63.0 - version: 10.63.0(patch_hash=e663994979ff877a26ab6d4dea5968fbaee4ccdfdeb85623983535a572678940) + specifier: 'catalog:' + version: 11.6.0 '@sentry/mcp-server': specifier: workspace:* version: link:../mcp-server '@sentry/node': - specifier: 10.65.0 - version: 10.65.0(@opentelemetry/core@2.11.0(@opentelemetry/api@1.9.1)) - '@sentry/node-core': - specifier: 10.63.0 - version: 10.63.0(patch_hash=a537ad4fb79b41d54b6294108829d470d10a45d3895041f16ab11168de0f1983)(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.220.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.11.0(@opentelemetry/api@1.9.1)) + specifier: 'catalog:' + version: 11.6.0(@babel/core@7.28.0)(rollup@4.62.4) '@sentry/sqlish': specifier: ^1.0.1 version: 1.0.1(react@19.2.8) @@ -4516,10 +4511,6 @@ packages: wrangler: optional: true - '@sentry/conventions@0.12.0': - resolution: {integrity: sha512-z1JQrl/1SLY+8wpzvork6vl+fpsg/oCCxM7HWWhUnI/R+OGNyoIzieQuggX3uUMY7NBtp8UWCQx6FeFazzOF9g==} - engines: {node: '>=14'} - '@sentry/conventions@0.15.1': resolution: {integrity: sha512-ZLP8bRdMON3prWE2tJyImuYscCxdcJeIPIhrOs/rgyFm3C1nCh1B6gdvPj3AZ5zW08oSFFCsq7T+tYEW3h8MNA==} engines: {node: '>=14'} @@ -4564,27 +4555,6 @@ packages: resolution: {integrity: sha512-V52/+SkMcPdlldExPC9TWS2sZ+CC+8BSzvVbRtkQxvy+jTOXFAJfInaVLPAAVt8W4WbPw9iAxVMzwHGaZ2muBw==} engines: {node: '>=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0'} - '@sentry/node-core@10.63.0': - resolution: {integrity: sha512-TaNtkGDRNxH3SjOea2PDtaebkNjMbAH8ZFsEcwlqmadpS7nqSR7z6slZy/iu7y1nLiUdbmcM5JmXwxksy52WRQ==} - engines: {node: '>=18'} - peerDependencies: - '@opentelemetry/api': ^1.9.0 - '@opentelemetry/core': ^1.30.1 || ^2.1.0 - '@opentelemetry/exporter-trace-otlp-http': '>=0.57.0 <1' - '@opentelemetry/instrumentation': '>=0.57.1 <1' - '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - '@opentelemetry/core': - optional: true - '@opentelemetry/exporter-trace-otlp-http': - optional: true - '@opentelemetry/instrumentation': - optional: true - '@opentelemetry/sdk-trace-base': - optional: true - '@sentry/node-core@10.65.0': resolution: {integrity: sha512-U01X9mPT+jZnsLPmPWfBU67Ka+t/Sdd9RGAuvGoKdrI6N47a/9PDkM9oCW+kj0fmZwogZHTgSnzJU5oi3pImgA==} engines: {node: '>=18'} @@ -4664,14 +4634,6 @@ packages: resolution: {integrity: sha512-5uHdUSACyodmtar+LxhFl4/MdICJ1/38mrp5lrMpDKfWSV/7tu/KRmr5gD2J9Nm47I/xxbghSz+oinJ4EtJatQ==} engines: {node: '>=20.19.0 <22.0.0 || >=22.12.0 <23.0.0 || >=23.2.0'} - '@sentry/opentelemetry@10.63.0': - resolution: {integrity: sha512-8yqi8+Ej/anmMn82blXA0BNMeAMs4av6nx0DzhxDrFya28ZaYOn19PChd3erMidfU0HnLLFNqWiFlYxBKq+/KA==} - engines: {node: '>=18'} - peerDependencies: - '@opentelemetry/api': ^1.9.0 - '@opentelemetry/core': ^1.30.1 || ^2.1.0 - '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 - '@sentry/opentelemetry@10.65.0': resolution: {integrity: sha512-8C6FPvm3XBvUrkM52dX3Gz0p2H0Ij8t4sahUA+GTiCz0WM0fnyPeQPGC/b6I4jamV9UXyCZRnE1UEEGCoD+c7A==} engines: {node: '>=18'} @@ -12751,15 +12713,13 @@ snapshots: '@cloudflare/workers-types': 4.20260405.1 wrangler: 4.151.3(@cloudflare/workers-types@4.20260405.1)(@types/node@24.13.5) - '@sentry/conventions@0.12.0': {} - '@sentry/conventions@0.15.1': {} '@sentry/conventions@0.16.0': {} '@sentry/conventions@0.26.0': {} - '@sentry/core@10.63.0(patch_hash=e663994979ff877a26ab6d4dea5968fbaee4ccdfdeb85623983535a572678940)': {} + '@sentry/core@10.63.0': {} '@sentry/core@10.65.0': dependencies: @@ -12787,18 +12747,6 @@ snapshots: dependencies: '@sentry/core': 11.6.0 - '@sentry/node-core@10.63.0(patch_hash=a537ad4fb79b41d54b6294108829d470d10a45d3895041f16ab11168de0f1983)(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.220.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.11.0(@opentelemetry/api@1.9.1))': - dependencies: - '@sentry/conventions': 0.12.0 - '@sentry/core': 10.63.0(patch_hash=e663994979ff877a26ab6d4dea5968fbaee4ccdfdeb85623983535a572678940) - '@sentry/opentelemetry': 10.63.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.11.0(@opentelemetry/api@1.9.1)) - import-in-the-middle: 3.5.1 - optionalDependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.11.0(@opentelemetry/api@1.9.1) - '@opentelemetry/instrumentation': 0.220.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.11.0(@opentelemetry/api@1.9.1) - '@sentry/node-core@10.65.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.220.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.11.0(@opentelemetry/api@1.9.1))': dependencies: '@sentry/conventions': 0.15.1 @@ -12897,14 +12845,6 @@ snapshots: - rollup - webpack - '@sentry/opentelemetry@10.63.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.11.0(@opentelemetry/api@1.9.1))': - dependencies: - '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.11.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-trace-base': 2.11.0(@opentelemetry/api@1.9.1) - '@sentry/conventions': 0.12.0 - '@sentry/core': 10.63.0(patch_hash=e663994979ff877a26ab6d4dea5968fbaee4ccdfdeb85623983535a572678940) - '@sentry/opentelemetry@10.65.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.11.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.11.0(@opentelemetry/api@1.9.1))': dependencies: '@opentelemetry/api': 1.9.1 @@ -13140,7 +13080,7 @@ snapshots: '@hono/node-server': 1.19.17(hono@4.13.8) '@jridgewell/trace-mapping': 0.3.31 '@modelcontextprotocol/sdk': 1.32.0(@cfworker/json-schema@4.1.1)(zod@4.6.5) - '@sentry/core': 10.63.0(patch_hash=e663994979ff877a26ab6d4dea5968fbaee4ccdfdeb85623983535a572678940) + '@sentry/core': 10.63.0 '@sentry/node': 10.65.0(@opentelemetry/core@2.11.0(@opentelemetry/api@1.9.1)) anser: 2.3.5 chalk: 5.6.2 @@ -13161,13 +13101,13 @@ snapshots: - hono-rate-limiter - supports-color - '@spotlightjs/spotlight@4.11.8(@cfworker/json-schema@4.1.1)(hono-rate-limiter@0.4.2(hono@4.13.8))': + '@spotlightjs/spotlight@4.11.8(@cfworker/json-schema@4.1.1)(@opentelemetry/core@2.11.0(@opentelemetry/api@1.9.1))(hono-rate-limiter@0.4.2(hono@4.13.8))': dependencies: '@hono/mcp': 0.2.5(@modelcontextprotocol/sdk@1.32.0(@cfworker/json-schema@4.1.1)(zod@4.6.5))(hono-rate-limiter@0.4.2(hono@4.13.8))(hono@4.13.8)(zod@4.6.5) '@hono/node-server': 1.19.17(hono@4.13.8) '@jridgewell/trace-mapping': 0.3.31 '@modelcontextprotocol/sdk': 1.32.0(@cfworker/json-schema@4.1.1)(zod@4.6.5) - '@sentry/core': 10.63.0(patch_hash=e663994979ff877a26ab6d4dea5968fbaee4ccdfdeb85623983535a572678940) + '@sentry/core': 10.63.0 '@sentry/node': 10.65.0(@opentelemetry/core@2.11.0(@opentelemetry/api@1.9.1)) anser: 2.3.5 chalk: 5.6.2 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index df8e40413..7f8f8dd0a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,8 +5,6 @@ packages: patchedDependencies: "@ai-sdk/provider-utils@2.2.8": patches/@ai-sdk__provider-utils@2.2.8.patch "@cloudflare/workers-oauth-provider@0.10.3": patches/@cloudflare__workers-oauth-provider@0.10.3.patch - "@sentry/core@10.63.0": packages/cli/patches/@sentry%2Fcore@10.63.0.patch - "@sentry/node-core@10.63.0": packages/cli/patches/@sentry%2Fnode-core@10.63.0.patch "@stricli/core@1.2.8": packages/cli/patches/@stricli%2Fcore@1.2.8.patch packageExtensions: From 4e5b317246b86a28291fbea834979403ff66435c Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 19:55:23 +0000 Subject: [PATCH 4/7] fix(cli): Use v11 SDK defaults for CLI telemetry Address review feedback on the v11 migration: - Use Sentry.init() defaults instead of a hand-maintained integration list; library mode uses the no-performance defaults and filters out integrations that hook the host process or its diagnostics channels. - Drop the v10 dataCollection baseline and attachStacktrace override. - Send informational unknown_command/invalid_argument events through captureEvent so attachStacktrace doesn't mark sessions errored. - Remove the VERCEL SIGTERM tests now that the SDK registers it once. - Fix stale Bun-binary comments and drop stale load-time figures. --- packages/cli/src/cli.ts | 8 +- packages/cli/src/lib/db/index.ts | 2 +- packages/cli/src/lib/error-reporting.ts | 5 +- packages/cli/src/lib/telemetry.ts | 86 ++++++------------- packages/cli/test/lib/error-reporting.test.ts | 7 +- .../lib/scanner-flags.integration.test.ts | 4 +- packages/cli/test/lib/telemetry.test.ts | 68 +++------------ 7 files changed, 58 insertions(+), 122 deletions(-) diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 3596639e8..90a7e8d94 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -2,7 +2,7 @@ * CLI runner with fast-path dispatch. * * Shell completion (`__complete`) is dispatched before any heavy imports - * to avoid loading `@sentry/node` (~280ms). All other commands go through + * to avoid loading `@sentry/node`. All other commands go through * the full CLI with telemetry, middleware, and error recovery. * * Extracted from `bin.ts` so the logic is testable and reusable without @@ -75,7 +75,7 @@ async function preloadProjectContext(cwd: string): Promise { * completion engine and SQLite cache modules. */ export async function runCompletion(completionArgs: string[]): Promise { - // Disable telemetry so db/index.ts skips the @sentry/node lazy-require (~280ms) + // Disable telemetry so db/index.ts skips the @sentry/node lazy-require getEnv().SENTRY_CLI_NO_TELEMETRY = "1"; const { handleComplete } = await import("./lib/complete.js"); handleComplete(completionArgs); @@ -665,7 +665,9 @@ export async function runCli(cliArgs: string[]): Promise { // Fixed message string so all unknown commands group into one Sentry // issue. The per-event detail (which token, suggestions) lives in the // structured context above and is queryable via Discover. - Sentry.captureMessage("unknown_command", "info"); + // captureEvent, not captureMessage: with `attachStacktrace` on, + // captureMessage adds a synthetic exception that marks the session errored. + Sentry.captureEvent({ message: "unknown_command", level: "info" }); } /** Build the command executor by composing error-recovery middlewares. */ diff --git a/packages/cli/src/lib/db/index.ts b/packages/cli/src/lib/db/index.ts index 6eb9b0b50..b99df122a 100644 --- a/packages/cli/src/lib/db/index.ts +++ b/packages/cli/src/lib/db/index.ts @@ -224,7 +224,7 @@ export function getDatabase(): Database { } // Wrap with tracing proxy for automatic query instrumentation. - // Lazy-require telemetry to avoid top-level import of @sentry/node (~85ms). + // Lazy-require telemetry to avoid top-level import of @sentry/node. // Shell completions set SENTRY_CLI_NO_TELEMETRY=1 to skip this entirely. if (getEnv().SENTRY_CLI_NO_TELEMETRY === "1") { db = rawDb; diff --git a/packages/cli/src/lib/error-reporting.ts b/packages/cli/src/lib/error-reporting.ts index dd59c52a7..5bf15ca82 100644 --- a/packages/cli/src/lib/error-reporting.ts +++ b/packages/cli/src/lib/error-reporting.ts @@ -491,7 +491,10 @@ function argumentErrorClassName(error: unknown): string { */ export function reportArgumentError(error: unknown, message: string): void { const errorClass = argumentErrorClassName(error); - Sentry.captureMessage("invalid_argument", { + // captureEvent, not captureMessage: with `attachStacktrace` on, + // captureMessage adds a synthetic exception that marks the session errored. + Sentry.captureEvent({ + message: "invalid_argument", level: "info", tags: { "argument_error.class": errorClass }, contexts: { diff --git a/packages/cli/src/lib/telemetry.ts b/packages/cli/src/lib/telemetry.ts index 135b29d00..e8519283d 100644 --- a/packages/cli/src/lib/telemetry.ts +++ b/packages/cli/src/lib/telemetry.ts @@ -11,7 +11,6 @@ import { chmodSync, statSync } from "node:fs"; import { createRequire } from "node:module"; -import type { Integration } from "@sentry/core"; // oxlint-disable-next-line sentry-cli/no-namespace-import -- Sentry SDK recommends namespace import import * as Sentry from "@sentry/node"; @@ -403,6 +402,8 @@ export function recordApiErrorOnSpan(span: Span, error: ApiError): void { const EXCLUDED_INTEGRATIONS = new Set([ "Console", // Captures console output - too noisy for CLI "Context", // Replaced below with cpu: false to avoid os.cpus() crash (CLI-1ED) + "Http", // Replaced below with spans: false + "NodeFetch", // Replaced below with spans: false "ContextLines", // Reads source files - we rely on uploaded sourcemaps instead "LocalVariables", // Captures local variables - adds significant overhead "Modules", // Lists all loaded modules - unnecessary for CLI telemetry @@ -424,58 +425,16 @@ const LIBRARY_EXCLUDED_INTEGRATIONS = new Set([ "NodeFetch", // diagnostics_channel + trace headers "FunctionToString", // wraps Function.prototype.toString "ChildProcess", // monitors child processes + "WorkerThreads", // diagnostics_channel on the host's worker threads "NodeRuntimeMetrics", // runtime metrics timer would keep host event loop alive + // Framework error handlers subscribe to the host's framework channels + "Express", + "Fastify", + "Hapi", + "Hono", + "Koa", ]); -/** - * The v10 `@sentry/node-core/light` default integration set, rebuilt for v11. - * - * v11's `Sentry.init()` adds dozens of framework, database, and AI - * auto-instrumentations that would bloat the bundle and, in library mode, - * instrument the host application. Outgoing HTTP spans stay off because - * the CLI creates its own `http.client` spans around API requests. - */ -function getCliDefaultIntegrations(): Integration[] { - return [ - Sentry.eventFiltersIntegration(), - Sentry.functionToStringIntegration(), - Sentry.linkedErrorsIntegration(), - Sentry.requestDataIntegration(), - Sentry.systemErrorIntegration(), - Sentry.consoleIntegration(), - Sentry.httpIntegration({ spans: false }), - Sentry.nativeNodeFetchIntegration({ spans: false }), - Sentry.onUncaughtExceptionIntegration(), - Sentry.onUnhandledRejectionIntegration(), - Sentry.contextLinesIntegration(), - Sentry.localVariablesIntegration(), - Sentry.nodeContextIntegration(), - Sentry.childProcessIntegration(), - Sentry.processSessionIntegration(), - Sentry.modulesIntegration(), - ]; -} - -/** - * v11 defaults that collect more than v10 did with `sendDefaultPii` unset. - * This is the v10 baseline from the v11 migration guide. - */ -const HEADER_DENYLIST = ["forwarded", "-ip", "remote-", "via", "-user"]; -const V10_DATA_COLLECTION: Sentry.NodeOptions["dataCollection"] = { - userInfo: false, - cookies: false, - httpHeaders: { - request: { deny: HEADER_DENYLIST }, - response: { deny: HEADER_DENYLIST }, - }, - httpBodies: [], - urlQueryParams: { deny: HEADER_DENYLIST }, - genAI: { inputs: false, outputs: false }, - databaseQueryData: false, - queues: false, - graphQL: { document: false, variables: false }, -}; - /** * Check whether `util.getSystemErrorMap` exists at setup time. * Bun does not implement this Node.js API, which the SDK's NodeSystemError @@ -636,17 +595,18 @@ export function initSentry( // close(0) removes listeners synchronously; we don't need to await the flush. Sentry.getClient()?.close(0); - const client = Sentry.initWithoutDefaultIntegrations({ + const client = Sentry.init({ dsn: SENTRY_CLI_DSN, enabled, - defaultIntegrations: getCliDefaultIntegrations(), + // The tracing integrations instrument database, framework, and AI + // libraries through diagnostics channels, which in library mode belong + // to the host application. + ...(libraryMode && { + defaultIntegrations: Sentry.getDefaultIntegrationsWithoutPerformance(), + }), // Keep v10's transaction model: dashboards and alerts rely on scope tags // on transactions, and beforeSendTransaction scrubs server_name. traceLifecycle: "static", - dataCollection: V10_DATA_COLLECTION, - // v11 defaults this to true, which would mark sessions errored on - // informational captureMessage calls such as `unknown_command`. - attachStacktrace: false, // The CLI ships as a bundle, so runtime module hooks can't instrument it; // in library mode they would instrument the host application instead. enableRuntimeChannelInjection: false, @@ -688,6 +648,16 @@ export function initSentry( ); } + // API requests already get an `http.client` span from the CLI, which + // also covers cache hits and retries. SDK spans would double-count + // them in span.op:http.client queries. + if (!libraryMode) { + filtered.push( + Sentry.httpIntegration({ spans: false }), + Sentry.nativeNodeFetchIntegration({ spans: false }), + ); + } + // Collect runtime metrics (CPU, memory, event loop) for non-library mode. // Uses nodeRuntimeMetricsIntegration which degrades gracefully on Bun: // monitorEventLoopDelay is try/caught, all other APIs are Bun-compatible. @@ -770,14 +740,14 @@ export function initSentry( const runtime = isBun ? "bun" : "node"; // NodeClient defaults runtime to { name: 'node' }. Override it so - // events carry the correct runtime when running as a compiled Bun binary. + // events carry the correct runtime when the npm package runs under Bun. const opts = client.getOptions(); opts.runtime = { name: runtime, version: isBun ? process.versions.bun : process.version, }; - // Tag whether running as bun binary or node (npm package). + // Tag whether running under Bun or Node. // Kept alongside the SDK's promoted 'runtime' tag for explicit signaling // and backward compatibility with existing dashboards/alerts. Sentry.setTag("cli.runtime", runtime); diff --git a/packages/cli/test/lib/error-reporting.test.ts b/packages/cli/test/lib/error-reporting.test.ts index d2eafb738..d4d2c5a86 100644 --- a/packages/cli/test/lib/error-reporting.test.ts +++ b/packages/cli/test/lib/error-reporting.test.ts @@ -834,7 +834,7 @@ describe("reportArgumentError", () => { let messageSpy: ReturnType; beforeEach(() => { - messageSpy = vi.spyOn(Sentry, "captureMessage"); + messageSpy = vi.spyOn(Sentry, "captureEvent"); }); afterEach(() => { @@ -848,7 +848,8 @@ describe("reportArgumentError", () => { new ValidationError("Invalid period 'bogus'", "period"), ); reportArgumentError(error, 'Failed to parse "bogus" for period'); - expect(messageSpy).toHaveBeenCalledWith("invalid_argument", { + expect(messageSpy).toHaveBeenCalledWith({ + message: "invalid_argument", level: "info", tags: { "argument_error.class": "ArgumentParseError" }, contexts: { @@ -867,8 +868,8 @@ describe("reportArgumentError", () => { "No flag registered for --nope", ); expect(messageSpy).toHaveBeenCalledWith( - "invalid_argument", expect.objectContaining({ + message: "invalid_argument", contexts: { invalid_argument: expect.objectContaining({ error_class: "FlagNotFoundError", diff --git a/packages/cli/test/lib/scanner-flags.integration.test.ts b/packages/cli/test/lib/scanner-flags.integration.test.ts index 707355494..54cee7bb9 100644 --- a/packages/cli/test/lib/scanner-flags.integration.test.ts +++ b/packages/cli/test/lib/scanner-flags.integration.test.ts @@ -336,12 +336,12 @@ describe("arguments that fail to parse", () => { }); test("is reported to Sentry for UX research", async () => { - const messageSpy = vi.spyOn(Sentry, "captureMessage"); + const messageSpy = vi.spyOn(Sentry, "captureEvent"); try { await runApp(["issue", "list", "--period", "bogus"]); expect(messageSpy).toHaveBeenCalledWith( - "invalid_argument", expect.objectContaining({ + message: "invalid_argument", contexts: { invalid_argument: expect.objectContaining({ cause_class: "ValidationError", diff --git a/packages/cli/test/lib/telemetry.test.ts b/packages/cli/test/lib/telemetry.test.ts index 2bfdd6a43..0ff66cd76 100644 --- a/packages/cli/test/lib/telemetry.test.ts +++ b/packages/cli/test/lib/telemetry.test.ts @@ -108,61 +108,21 @@ describe("initSentry", () => { expect(client?.getOptions().release).toBe("0.0.0-dev"); }); - test("library mode installs only error-capture integrations", () => { + test("library mode leaves the host's process hooks and channels alone", () => { const client = initSentry(true, { libraryMode: true }); - const names = client?.getOptions().integrations.map((i) => i.name); - expect(names?.sort()).toEqual([ - "EventFilters", - "LinkedErrors", - "LocalVariablesAsync", - "NodeSystemError", - "RequestData", - ]); - }); - - describe("under VERCEL", () => { - let originalVercel: string | undefined; - let preTestSigterm: Set; - - beforeEach(() => { - originalVercel = process.env.VERCEL; - process.env.VERCEL = "1"; - preTestSigterm = new Set(process.rawListeners("SIGTERM")); - }); - - afterEach(() => { - if (originalVercel === undefined) { - delete process.env.VERCEL; - } else { - process.env.VERCEL = originalVercel; - } - for (const listener of process.rawListeners("SIGTERM")) { - if (!preTestSigterm.has(listener)) { - process.removeListener( - "SIGTERM", - listener as (...args: unknown[]) => void, - ); - } - } - }); - - test("keeps one SDK SIGTERM listener across repeated inits", () => { - const baseline = process.listenerCount("SIGTERM"); - for (let i = 0; i < 15; i++) { - initSentry(i % 2 === 0, { libraryMode: true }); - } - expect(process.listenerCount("SIGTERM")).toBe(baseline + 1); - }); - - test("preserves SIGTERM listeners it did not register", () => { - const hostListener = () => { - // host-owned handler - }; - process.on("SIGTERM", hostListener); - initSentry(true, { libraryMode: true }); - initSentry(true, { libraryMode: true }); - expect(process.listeners("SIGTERM")).toContain(hostListener); - }); + const names = client?.getOptions().integrations.map((i) => i.name) ?? []; + for (const hostHook of [ + "OnUncaughtException", + "OnUnhandledRejection", + "Http", + "NodeFetch", + "WorkerThreads", + "Express", + "Postgres", + "OpenAI", + ]) { + expect(names).not.toContain(hostHook); + } }); }); From 898450fe08b072b0aa6e7c2d3860e28eabcdb668 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 20:21:36 +0000 Subject: [PATCH 5/7] fix(cli): Use SDK HTTP spans and strip channel injection at build time - Drop the CLI's own http.client spans (API client, OAuth) and manual sentry-trace/baggage injection. They existed because Bun's fetch didn't fire undici diagnostics channels; the CLI now runs on Node, so the SDK's Http/NodeFetch integrations cover both. - Set the SDK's __SENTRY_CHANNEL_INJECTION__ build define to false so esbuild tree-shakes the runtime module hooks from shipped bundles. Co-Authored-By: Burak Yigit Kaya <126780+BYK@users.noreply.github.com> --- packages/cli/script/build.ts | 3 + packages/cli/script/bundle.ts | 3 + packages/cli/src/lib/oauth.ts | 285 ++++++++++++------------ packages/cli/src/lib/sentry-client.ts | 151 +++++-------- packages/cli/src/lib/telemetry.ts | 40 +--- packages/cli/test/lib/telemetry.test.ts | 16 -- 6 files changed, 207 insertions(+), 291 deletions(-) diff --git a/packages/cli/script/build.ts b/packages/cli/script/build.ts index 918bf82f4..091544820 100644 --- a/packages/cli/script/build.ts +++ b/packages/cli/script/build.ts @@ -154,6 +154,9 @@ async function bundleJs(): Promise { SENTRY_CLIENT_ID_BUILD: JSON.stringify(SENTRY_CLIENT_ID), "process.env.NODE_ENV": JSON.stringify("production"), __SENTRY_DEBUG_ID__: JSON.stringify(PLACEHOLDER_DEBUG_ID), + // Tree-shakes the SDK's runtime module hooks, which can't instrument a + // bundle and would hook the host app in library mode. + __SENTRY_CHANNEL_INJECTION__: "false", }, plugins: [ textImportPlugin, diff --git a/packages/cli/script/bundle.ts b/packages/cli/script/bundle.ts index ad744b31f..86d71dbac 100644 --- a/packages/cli/script/bundle.ts +++ b/packages/cli/script/bundle.ts @@ -311,6 +311,9 @@ const commonDefine: Record = { SENTRY_CLIENT_ID_BUILD: JSON.stringify(SENTRY_CLIENT_ID), "process.env.NODE_ENV": JSON.stringify("production"), __SENTRY_DEBUG_ID__: JSON.stringify(PLACEHOLDER_DEBUG_ID), + // Tree-shakes the SDK's runtime module hooks, which can't instrument a + // bundle and would hook the host app in library mode. + __SENTRY_CHANNEL_INJECTION__: "false", }; // In an ESM bundle, `require`, `__dirname`, and `__filename` don't exist, but diff --git a/packages/cli/src/lib/oauth.ts b/packages/cli/src/lib/oauth.ts index 5e1373a81..4fa143a3e 100644 --- a/packages/cli/src/lib/oauth.ts +++ b/packages/cli/src/lib/oauth.ts @@ -37,7 +37,6 @@ import { } from "./errors.js"; import { logger } from "./logger.js"; import { normalizeHttpOrigin } from "./sentry-urls.js"; -import { withHttpSpan } from "./telemetry.js"; import { getActiveTokenHost } from "./token-host.js"; /** @@ -272,105 +271,101 @@ function assertRefreshHostTrusted(refreshUrl: string): string { * @param scope - Space-joined scope string to request. Defaults to the full * {@link SCOPES} set. Use {@link resolveOAuthScopeString} to build it. */ -function requestDeviceCode(scope: string = SCOPES) { +async function requestDeviceCode(scope: string = SCOPES) { const clientId = getClientId(); - return withHttpSpan("POST", "/oauth/device/code/", async () => { - const response = await fetchWithConnectionError( - `${getSentryUrl()}/oauth/device/code/`, - { - method: "POST", - headers: { "Content-Type": "application/x-www-form-urlencoded" }, - body: deviceCodeRequestBody(clientId, scope), - }, - ); + const response = await fetchWithConnectionError( + `${getSentryUrl()}/oauth/device/code/`, + { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: deviceCodeRequestBody(clientId, scope), + }, + ); - if (!response.ok) { - const errorText = await response.text(); - throw new ApiError( - "Failed to initiate device flow", - response.status, - errorText, - "/oauth/device/code/", - ); - } + if (!response.ok) { + const errorText = await response.text(); + throw new ApiError( + "Failed to initiate device flow", + response.status, + errorText, + "/oauth/device/code/", + ); + } - let data: unknown; - try { - data = await response.json(); - } catch (parseError) { - logger.debug("Non-JSON response from device code endpoint", parseError); - throw new ApiError( - "Invalid response from device authorization endpoint", - response.status, - "The server returned a non-JSON response body.", - "/oauth/device/code/", - ); - } + let data: unknown; + try { + data = await response.json(); + } catch (parseError) { + logger.debug("Non-JSON response from device code endpoint", parseError); + throw new ApiError( + "Invalid response from device authorization endpoint", + response.status, + "The server returned a non-JSON response body.", + "/oauth/device/code/", + ); + } - const result = safeParse(DeviceCodeResponseSchema, data); - if (!result.success) { - throw new ApiError( - "Invalid response from device authorization endpoint", - response.status, - result.issues.map((i) => i.message).join(", "), - "/oauth/device/code/", - ); - } + const result = safeParse(DeviceCodeResponseSchema, data); + if (!result.success) { + throw new ApiError( + "Invalid response from device authorization endpoint", + response.status, + result.issues.map((i) => i.message).join(", "), + "/oauth/device/code/", + ); + } - return result.output; - }); + return result.output; } /** * Poll Sentry's token endpoint for the access token */ -function pollForToken(deviceCode: string): Promise { - return withHttpSpan("POST", "/oauth/token/", async () => { - const response = await fetchWithConnectionError( - `${getSentryUrl()}/oauth/token/`, - { - method: "POST", - headers: { "Content-Type": "application/x-www-form-urlencoded" }, - body: deviceTokenRequestBody(getClientId(), deviceCode), - }, - ); - - let data: unknown; - try { - data = await response.json(); - } catch (parseError) { - logger.debug("Non-JSON response from token endpoint", parseError); - throw new ApiError( - "Unexpected response from token endpoint", - response.status, - "The server returned a non-JSON response body (possible proxy or CDN issue).", - "/oauth/token/", - ); - } - - // Try to parse as success response first - const tokenResult = safeParse(TokenResponseSchema, data); - if (tokenResult.success) { - return tokenResult.output; - } - - // Try to parse as error response - const errorResult = safeParse(TokenErrorResponseSchema, data); - if (errorResult.success) { - throw new DeviceFlowError( - errorResult.output.error, - errorResult.output.error_description, - ); - } +async function pollForToken(deviceCode: string): Promise { + const response = await fetchWithConnectionError( + `${getSentryUrl()}/oauth/token/`, + { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: deviceTokenRequestBody(getClientId(), deviceCode), + }, + ); - // If neither schema matches, throw a generic error + let data: unknown; + try { + data = await response.json(); + } catch (parseError) { + logger.debug("Non-JSON response from token endpoint", parseError); throw new ApiError( "Unexpected response from token endpoint", response.status, - JSON.stringify(data), + "The server returned a non-JSON response body (possible proxy or CDN issue).", "/oauth/token/", ); - }); + } + + // Try to parse as success response first + const tokenResult = safeParse(TokenResponseSchema, data); + if (tokenResult.success) { + return tokenResult.output; + } + + // Try to parse as error response + const errorResult = safeParse(TokenErrorResponseSchema, data); + if (errorResult.success) { + throw new DeviceFlowError( + errorResult.output.error, + errorResult.output.error_description, + ); + } + + // If neither schema matches, throw a generic error + throw new ApiError( + "Unexpected response from token endpoint", + response.status, + JSON.stringify(data), + "/oauth/token/", + ); } type PollResult = @@ -541,80 +536,78 @@ export async function setApiToken( } /** Refresh an access token using a refresh token. */ -export function refreshAccessToken( +export async function refreshAccessToken( refreshToken: string, options: { credentialHost: string }, ): Promise { const clientId = getClientId(); const credentialHost = assertRefreshHostTrusted(options.credentialHost); - return withHttpSpan("POST", "/oauth/token/", async () => { - const response = await fetchWithConnectionError( - `${credentialHost}/oauth/token/`, - { - method: "POST", - // Never replay a refresh credential to a server-selected destination. - redirect: "error", - headers: { "Content-Type": "application/x-www-form-urlencoded" }, - body: new URLSearchParams({ - client_id: clientId, - grant_type: "refresh_token", - refresh_token: refreshToken, - }), - }, - true, - ); + const response = await fetchWithConnectionError( + `${credentialHost}/oauth/token/`, + { + method: "POST", + // Never replay a refresh credential to a server-selected destination. + redirect: "error", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ + client_id: clientId, + grant_type: "refresh_token", + refresh_token: refreshToken, + }), + }, + true, + ); - if (!response.ok) { - let rejected = false; - try { - const errorResult = safeParse( - TokenErrorResponseSchema, - await response.json(), - ); - rejected = - errorResult.success && errorResult.output.error === "invalid_grant"; - } catch (error) { - logger.debug("Failed to parse token refresh error response", error); - } - if (rejected) { - throw new AuthError( - "expired", - "Session expired because the refresh credential was rejected. Run 'sentry auth login' to re-authenticate.", - ); - } - throw new ApiError( - "Token refresh failed", - response.status, - "The refresh endpoint returned an unexpected failure.", - "/oauth/token/", + if (!response.ok) { + let rejected = false; + try { + const errorResult = safeParse( + TokenErrorResponseSchema, + await response.json(), ); + rejected = + errorResult.success && errorResult.output.error === "invalid_grant"; + } catch (error) { + logger.debug("Failed to parse token refresh error response", error); } - - let data: unknown; - try { - data = await response.json(); - } catch (parseError) { - logger.debug("Non-JSON response from token refresh endpoint", parseError); - throw new ApiError( - "Unexpected response from token refresh endpoint", - response.status, - "The server returned a non-JSON response body (possible proxy or CDN issue).", - "/oauth/token/", + if (rejected) { + throw new AuthError( + "expired", + "Session expired because the refresh credential was rejected. Run 'sentry auth login' to re-authenticate.", ); } + throw new ApiError( + "Token refresh failed", + response.status, + "The refresh endpoint returned an unexpected failure.", + "/oauth/token/", + ); + } + + let data: unknown; + try { + data = await response.json(); + } catch (parseError) { + logger.debug("Non-JSON response from token refresh endpoint", parseError); + throw new ApiError( + "Unexpected response from token refresh endpoint", + response.status, + "The server returned a non-JSON response body (possible proxy or CDN issue).", + "/oauth/token/", + ); + } - const result = safeParse(TokenResponseSchema, data); + const result = safeParse(TokenResponseSchema, data); - if (!result.success) { - throw new ApiError( - "Invalid response from token refresh endpoint", - response.status, - result.issues.map((i) => i.message).join(", "), - "/oauth/token/", - ); - } + if (!result.success) { + throw new ApiError( + "Invalid response from token refresh endpoint", + response.status, + result.issues.map((i) => i.message).join(", "), + "/oauth/token/", + ); + } - return result.output; - }); + return result.output; } diff --git a/packages/cli/src/lib/sentry-client.ts b/packages/cli/src/lib/sentry-client.ts index 5b520bd13..f3976f894 100644 --- a/packages/cli/src/lib/sentry-client.ts +++ b/packages/cli/src/lib/sentry-client.ts @@ -10,7 +10,6 @@ import { setTimeout as sleepMs } from "node:timers/promises"; import type { SentryClient } from "@sentry/api"; -import { getTraceData } from "@sentry/node"; import { computeInvalidationPrefixes } from "@sentry/toolkit-core/response-cache"; import { formatAuthHeader, @@ -59,7 +58,6 @@ import { isRetryableMethod, } from "./retry-policy.js"; import { normalizeHttpOrigin, normalizeOrigin } from "./sentry-urls.js"; -import { withTracingSpan } from "./telemetry.js"; import { parseSntrysClaim } from "./token-claims.js"; import { isHostTrustedForClaim, @@ -227,18 +225,6 @@ function prepareHeaders( headers.set("User-Agent", getUserAgent()); } - // Inject distributed tracing headers to connect CLI spans to backend traces. - // Manual injection is required because Bun's fetch doesn't fire undici - // diagnostics channels, so the SDK's nativeNodeFetchIntegration cannot work. - // When telemetry is disabled, getTraceData() returns {} — no headers injected. - const traceData = getTraceData(); - if (traceData["sentry-trace"]) { - headers.set("sentry-trace", traceData["sentry-trace"]); - } - if (traceData.baggage) { - headers.set("baggage", traceData.baggage); - } - // Inject user-configured custom headers for self-hosted proxies (IAP, // mTLS, etc.) — scoped to the request URL. applyCustomHeaders( @@ -519,7 +505,7 @@ function handleFetchError( return { action: "retry", delayMs: backoffDelayMs(attempt, MAX_BACKOFF_MS) }; } -/** Extract the URL pathname for span naming */ +/** Extract the URL pathname for logging and cache keys */ function extractUrlPath(input: Request | string | URL): string { const raw = extractInputUrl(input); // oxlint-disable-next-line sentry-cli/no-silent-catch -- grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. @@ -772,7 +758,6 @@ async function fetchWithRetry({ * - 401 handling: force-refreshes the token and retries once (any method) * - Jittered exponential backoff between retries * - User-Agent header for API analytics - * - Automatic HTTP span tracing for every request * * Cache is checked first — on a hit, auth refresh, timeout, and retry logic are * all skipped. On a miss or for non-GET methods, the full authenticated flow runs @@ -796,7 +781,7 @@ type AuthenticatedFetchOptions = { function createAuthenticatedFetch( options: AuthenticatedFetchOptions = {}, ): (input: Request | string | URL, init?: RequestInit) => Promise { - return function authenticatedFetch( + return async function authenticatedFetch( input: Request | string | URL, init?: RequestInit, ): Promise { @@ -815,86 +800,68 @@ function createAuthenticatedFetch( init?.method ?? (input instanceof Request ? input.method : "GET"); const urlPath = extractUrlPath(input); - return withTracingSpan( - `${method} ${urlPath}`, - "http.client", - async (span) => { - const fullUrl = extractInputUrl(input); - const startTime = performance.now(); - - // Check cache before auth/retry for GET requests. - // Uses current token (no refresh) so lookups are fast but Vary-correct. - const credential = options.credential ?? getCredentialContext(); - if (!credential) { - await refreshToken(); - throw new Error( - "Authentication state was not available after refresh", - ); - } - // A synthetic HTTP cache response has no validated final-origin or - // credential provenance. Discovery must obtain both from the network. - const lookup = options.validatedRedirects - ? undefined - : await tryCacheLookup( - method, - fullUrl, - authHeaders(credential.token), - credential.identity, - ); - if (lookup?.kind === "hit") { - span.setAttribute( - "http.response.status_code", - lookup.response.status, - ); - log.debug( - `${method} ${urlPath} → ${lookup.response.status} (cache hit, ${(performance.now() - startTime).toFixed(0)}ms)`, - ); - return lookup.response; - } - - const retryContext: RetryContext = { - input, - init, + const fullUrl = extractInputUrl(input); + const startTime = performance.now(); + + // Check cache before auth/retry for GET requests. + // Uses current token (no refresh) so lookups are fast but Vary-correct. + const credential = options.credential ?? getCredentialContext(); + if (!credential) { + await refreshToken(); + throw new Error("Authentication state was not available after refresh"); + } + // A synthetic HTTP cache response has no validated final-origin or + // credential provenance. Discovery must obtain both from the network. + const lookup = options.validatedRedirects + ? undefined + : await tryCacheLookup( method, fullUrl, - credential, - validatedRedirects: options.validatedRedirects === true, - idempotent: options.idempotent === true, - responseBodyMode: - method.toUpperCase() !== "GET" - ? "stream" - : ((input instanceof Request - ? sdkResponseBodyModes.get(input) - : undefined) ?? - (options.bufferResponseBody ? "buffer" : "stream")), - }; - let response = await fetchWithRetry({ - ...retryContext, - conditionalHeaders: lookup?.conditionalHeaders, - }); - if (lookup?.kind === "stale" && response.status === 304) { - const revalidated = await lookup.revalidate(response); - if (revalidated) { - span.setAttribute("http.response.status_code", revalidated.status); - log.debug( - `${method} ${urlPath} → 304 (revalidated cache entry, ${(performance.now() - startTime).toFixed(0)}ms)`, - ); - return revalidated; - } - // A 304 the entry cannot use has no body; fetch it unconditionally. - response = await fetchWithRetry(retryContext); - } - span.setAttribute("http.response.status_code", response.status); - if (!response.ok) { - span.setStatus({ code: 2, message: `${response.status}` }); - } + authHeaders(credential.token), + credential.identity, + ); + if (lookup?.kind === "hit") { + log.debug( + `${method} ${urlPath} → ${lookup.response.status} (cache hit, ${(performance.now() - startTime).toFixed(0)}ms)`, + ); + return lookup.response; + } + + const retryContext: RetryContext = { + input, + init, + method, + fullUrl, + credential, + validatedRedirects: options.validatedRedirects === true, + idempotent: options.idempotent === true, + responseBodyMode: + method.toUpperCase() !== "GET" + ? "stream" + : ((input instanceof Request + ? sdkResponseBodyModes.get(input) + : undefined) ?? + (options.bufferResponseBody ? "buffer" : "stream")), + }; + let response = await fetchWithRetry({ + ...retryContext, + conditionalHeaders: lookup?.conditionalHeaders, + }); + if (lookup?.kind === "stale" && response.status === 304) { + const revalidated = await lookup.revalidate(response); + if (revalidated) { log.debug( - `${method} ${urlPath} → ${response.status} (${(performance.now() - startTime).toFixed(0)}ms)`, + `${method} ${urlPath} → 304 (revalidated cache entry, ${(performance.now() - startTime).toFixed(0)}ms)`, ); - return response; - }, - { "http.request.method": method, "url.path": urlPath }, + return revalidated; + } + // A 304 the entry cannot use has no body; fetch it unconditionally. + response = await fetchWithRetry(retryContext); + } + log.debug( + `${method} ${urlPath} → ${response.status} (${(performance.now() - startTime).toFixed(0)}ms)`, ); + return response; }; } diff --git a/packages/cli/src/lib/telemetry.ts b/packages/cli/src/lib/telemetry.ts index e8519283d..c46cd278a 100644 --- a/packages/cli/src/lib/telemetry.ts +++ b/packages/cli/src/lib/telemetry.ts @@ -402,8 +402,6 @@ export function recordApiErrorOnSpan(span: Span, error: ApiError): void { const EXCLUDED_INTEGRATIONS = new Set([ "Console", // Captures console output - too noisy for CLI "Context", // Replaced below with cpu: false to avoid os.cpus() crash (CLI-1ED) - "Http", // Replaced below with spans: false - "NodeFetch", // Replaced below with spans: false "ContextLines", // Reads source files - we rely on uploaded sourcemaps instead "LocalVariables", // Captures local variables - adds significant overhead "Modules", // Lists all loaded modules - unnecessary for CLI telemetry @@ -607,8 +605,8 @@ export function initSentry( // Keep v10's transaction model: dashboards and alerts rely on scope tags // on transactions, and beforeSendTransaction scrubs server_name. traceLifecycle: "static", - // The CLI ships as a bundle, so runtime module hooks can't instrument it; - // in library mode they would instrument the host application instead. + // Shipped bundles strip these hooks via the `__SENTRY_CHANNEL_INJECTION__` + // build define; this keeps unbundled runs (dev, tests) consistent. enableRuntimeChannelInjection: false, // Compress outgoing envelopes with zstd (level 3) instead of gzip — // smaller payloads, faster compress/decompress on both sides. @@ -648,16 +646,6 @@ export function initSentry( ); } - // API requests already get an `http.client` span from the CLI, which - // also covers cache hits and retries. SDK spans would double-count - // them in span.op:http.client queries. - if (!libraryMode) { - filtered.push( - Sentry.httpIntegration({ spans: false }), - Sentry.nativeNodeFetchIntegration({ spans: false }), - ); - } - // Collect runtime metrics (CPU, memory, event loop) for non-library mode. // Uses nodeRuntimeMetricsIntegration which degrades gracefully on Bun: // monitorEventLoopDelay is try/caught, all other APIs are Bun-compatible. @@ -1044,7 +1032,7 @@ export function createWizardPromptTelemetry(): WizardPromptTelemetry { * to OK on success or Error on failure. * * Use this generic helper for custom operations, or use the specialized - * helpers (withHttpSpan, withDbSpan, withFsSpan, withSerializeSpan) for + * helpers (withDbSpan, withFsSpan, withSerializeSpan) for * common operation types. * * @param name - Span name (e.g., "scanDirectory", "findProjectRoot") @@ -1135,28 +1123,6 @@ export function withTracingSpan( ); } -/** - * Wrap an HTTP request with a span for tracing. - * - * Creates a child span under the current active span to track - * HTTP request duration and status. - * - * @param method - HTTP method (GET, POST, etc.) - * @param url - Request URL or path - * @param fn - The async function that performs the HTTP request - * @returns The result of the function - */ -export function withHttpSpan( - method: string, - url: string, - fn: () => Promise, -): Promise { - return withTracing(`${method} ${url}`, "http.client", fn, { - "http.request.method": method, - "url.path": url, - }); -} - /** * Wrap a database operation with a span for tracing. * diff --git a/packages/cli/test/lib/telemetry.test.ts b/packages/cli/test/lib/telemetry.test.ts index 0ff66cd76..113c91066 100644 --- a/packages/cli/test/lib/telemetry.test.ts +++ b/packages/cli/test/lib/telemetry.test.ts @@ -41,7 +41,6 @@ import { setOrgProjectContext, withDbSpan, withFsSpan, - withHttpSpan, withSerializeSpan, withTelemetry, withTracing, @@ -851,21 +850,6 @@ describe("setArgsContext", () => { }); }); -describe("withHttpSpan", () => { - test("executes function and returns result", async () => { - const result = await withHttpSpan("GET", "/test", async () => "success"); - expect(result).toBe("success"); - }); - - test("propagates errors", async () => { - await expect( - withHttpSpan("POST", "/test", async () => { - throw new Error("http error"); - }), - ).rejects.toThrow("http error"); - }); -}); - describe("withDbSpan", () => { test("executes function and returns result", () => { const result = withDbSpan("testOp", () => 42); From 8911ed37f414a3a2d900828f7f15fdd70c5f5365 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 20:38:12 +0000 Subject: [PATCH 6/7] fix(cli): Keep API request tracing in library mode Library mode excludes the SDK's Http/NodeFetch integrations so it doesn't trace the host's traffic, and Bun's fetch doesn't fire the undici channels they use. In those cases the API client adds its own http.client span and sentry-trace/baggage headers again; Node CLI runs keep relying on the SDK. --- packages/cli/src/cli.ts | 4 +- packages/cli/src/lib/db/index.ts | 2 +- packages/cli/src/lib/sentry-client.ts | 141 ++++++++++++-------- packages/cli/src/lib/telemetry.ts | 14 ++ packages/cli/test/lib/sentry-client.test.ts | 50 +++++++ 5 files changed, 153 insertions(+), 58 deletions(-) diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 90a7e8d94..7a9dce272 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -2,7 +2,7 @@ * CLI runner with fast-path dispatch. * * Shell completion (`__complete`) is dispatched before any heavy imports - * to avoid loading `@sentry/node`. All other commands go through + * to avoid loading `@sentry/node` (~45ms in the bundle). All other commands go through * the full CLI with telemetry, middleware, and error recovery. * * Extracted from `bin.ts` so the logic is testable and reusable without @@ -75,7 +75,7 @@ async function preloadProjectContext(cwd: string): Promise { * completion engine and SQLite cache modules. */ export async function runCompletion(completionArgs: string[]): Promise { - // Disable telemetry so db/index.ts skips the @sentry/node lazy-require + // Disable telemetry so db/index.ts skips the @sentry/node lazy-require (~45ms) getEnv().SENTRY_CLI_NO_TELEMETRY = "1"; const { handleComplete } = await import("./lib/complete.js"); handleComplete(completionArgs); diff --git a/packages/cli/src/lib/db/index.ts b/packages/cli/src/lib/db/index.ts index b99df122a..19c99939e 100644 --- a/packages/cli/src/lib/db/index.ts +++ b/packages/cli/src/lib/db/index.ts @@ -224,7 +224,7 @@ export function getDatabase(): Database { } // Wrap with tracing proxy for automatic query instrumentation. - // Lazy-require telemetry to avoid top-level import of @sentry/node. + // Lazy-require telemetry to avoid top-level import of @sentry/node (~45ms). // Shell completions set SENTRY_CLI_NO_TELEMETRY=1 to skip this entirely. if (getEnv().SENTRY_CLI_NO_TELEMETRY === "1") { db = rawDb; diff --git a/packages/cli/src/lib/sentry-client.ts b/packages/cli/src/lib/sentry-client.ts index f3976f894..e3543441a 100644 --- a/packages/cli/src/lib/sentry-client.ts +++ b/packages/cli/src/lib/sentry-client.ts @@ -10,6 +10,7 @@ import { setTimeout as sleepMs } from "node:timers/promises"; import type { SentryClient } from "@sentry/api"; +import { getTraceData } from "@sentry/node"; import { computeInvalidationPrefixes } from "@sentry/toolkit-core/response-cache"; import { formatAuthHeader, @@ -58,6 +59,7 @@ import { isRetryableMethod, } from "./retry-policy.js"; import { normalizeHttpOrigin, normalizeOrigin } from "./sentry-urls.js"; +import { isManualHttpTracing, withTracingSpan } from "./telemetry.js"; import { parseSntrysClaim } from "./token-claims.js"; import { isHostTrustedForClaim, @@ -225,6 +227,16 @@ function prepareHeaders( headers.set("User-Agent", getUserAgent()); } + if (isManualHttpTracing()) { + const traceData = getTraceData(); + if (traceData["sentry-trace"]) { + headers.set("sentry-trace", traceData["sentry-trace"]); + } + if (traceData.baggage) { + headers.set("baggage", traceData.baggage); + } + } + // Inject user-configured custom headers for self-hosted proxies (IAP, // mTLS, etc.) — scoped to the request URL. applyCustomHeaders( @@ -800,68 +812,87 @@ function createAuthenticatedFetch( init?.method ?? (input instanceof Request ? input.method : "GET"); const urlPath = extractUrlPath(input); - const fullUrl = extractInputUrl(input); - const startTime = performance.now(); + const request = async (): Promise => { + const fullUrl = extractInputUrl(input); + const startTime = performance.now(); - // Check cache before auth/retry for GET requests. - // Uses current token (no refresh) so lookups are fast but Vary-correct. - const credential = options.credential ?? getCredentialContext(); - if (!credential) { - await refreshToken(); - throw new Error("Authentication state was not available after refresh"); - } - // A synthetic HTTP cache response has no validated final-origin or - // credential provenance. Discovery must obtain both from the network. - const lookup = options.validatedRedirects - ? undefined - : await tryCacheLookup( - method, - fullUrl, - authHeaders(credential.token), - credential.identity, + // Check cache before auth/retry for GET requests. + // Uses current token (no refresh) so lookups are fast but Vary-correct. + const credential = options.credential ?? getCredentialContext(); + if (!credential) { + await refreshToken(); + throw new Error("Authentication state was not available after refresh"); + } + // A synthetic HTTP cache response has no validated final-origin or + // credential provenance. Discovery must obtain both from the network. + const lookup = options.validatedRedirects + ? undefined + : await tryCacheLookup( + method, + fullUrl, + authHeaders(credential.token), + credential.identity, + ); + if (lookup?.kind === "hit") { + log.debug( + `${method} ${urlPath} → ${lookup.response.status} (cache hit, ${(performance.now() - startTime).toFixed(0)}ms)`, ); - if (lookup?.kind === "hit") { + return lookup.response; + } + + const retryContext: RetryContext = { + input, + init, + method, + fullUrl, + credential, + validatedRedirects: options.validatedRedirects === true, + idempotent: options.idempotent === true, + responseBodyMode: + method.toUpperCase() !== "GET" + ? "stream" + : ((input instanceof Request + ? sdkResponseBodyModes.get(input) + : undefined) ?? + (options.bufferResponseBody ? "buffer" : "stream")), + }; + let response = await fetchWithRetry({ + ...retryContext, + conditionalHeaders: lookup?.conditionalHeaders, + }); + if (lookup?.kind === "stale" && response.status === 304) { + const revalidated = await lookup.revalidate(response); + if (revalidated) { + log.debug( + `${method} ${urlPath} → 304 (revalidated cache entry, ${(performance.now() - startTime).toFixed(0)}ms)`, + ); + return revalidated; + } + // A 304 the entry cannot use has no body; fetch it unconditionally. + response = await fetchWithRetry(retryContext); + } log.debug( - `${method} ${urlPath} → ${lookup.response.status} (cache hit, ${(performance.now() - startTime).toFixed(0)}ms)`, + `${method} ${urlPath} → ${response.status} (${(performance.now() - startTime).toFixed(0)}ms)`, ); - return lookup.response; - } - - const retryContext: RetryContext = { - input, - init, - method, - fullUrl, - credential, - validatedRedirects: options.validatedRedirects === true, - idempotent: options.idempotent === true, - responseBodyMode: - method.toUpperCase() !== "GET" - ? "stream" - : ((input instanceof Request - ? sdkResponseBodyModes.get(input) - : undefined) ?? - (options.bufferResponseBody ? "buffer" : "stream")), + return response; }; - let response = await fetchWithRetry({ - ...retryContext, - conditionalHeaders: lookup?.conditionalHeaders, - }); - if (lookup?.kind === "stale" && response.status === 304) { - const revalidated = await lookup.revalidate(response); - if (revalidated) { - log.debug( - `${method} ${urlPath} → 304 (revalidated cache entry, ${(performance.now() - startTime).toFixed(0)}ms)`, - ); - return revalidated; - } - // A 304 the entry cannot use has no body; fetch it unconditionally. - response = await fetchWithRetry(retryContext); + + if (!isManualHttpTracing()) { + return await request(); } - log.debug( - `${method} ${urlPath} → ${response.status} (${(performance.now() - startTime).toFixed(0)}ms)`, + return await withTracingSpan( + `${method} ${urlPath}`, + "http.client", + async (span) => { + const response = await request(); + span.setAttribute("http.response.status_code", response.status); + if (!response.ok) { + span.setStatus({ code: 2, message: `${response.status}` }); + } + return response; + }, + { "http.request.method": method, "url.path": urlPath }, ); - return response; }; } diff --git a/packages/cli/src/lib/telemetry.ts b/packages/cli/src/lib/telemetry.ts index c46cd278a..b3ddc694a 100644 --- a/packages/cli/src/lib/telemetry.ts +++ b/packages/cli/src/lib/telemetry.ts @@ -455,6 +455,18 @@ const hasGetSystemErrorMap = (() => { /** Current beforeExit handler, tracked so it can be replaced on re-init */ let currentBeforeExitHandler: (() => void) | null = null; +let manualHttpTracing = false; + +/** + * Whether API requests need CLI-made `http.client` spans and trace headers. + * Library mode excludes the SDK's Http/NodeFetch integrations because they + * would also trace the host's requests, and Bun's fetch doesn't fire the + * undici diagnostics channels they listen on. + */ +export function isManualHttpTracing(): boolean { + return manualHttpTracing; +} + /** Match all SaaS regional URLs (us.sentry.io, de.sentry.io, o1234.ingest.us.sentry.io, etc.) */ const SENTRY_SAAS_SUBDOMAIN_RE = /^https:\/\/[^/]*\.sentry\.io(\/|$)/; @@ -532,6 +544,8 @@ export function initSentry( ): Sentry.NodeClient | undefined { const libraryMode = options?.libraryMode ?? false; const environment = getCliEnvironment(); + manualHttpTracing = + libraryMode || typeof process.versions.bun !== "undefined"; /** * Ensure frame paths are absolute so Sentry's symbolicator can match them. diff --git a/packages/cli/test/lib/sentry-client.test.ts b/packages/cli/test/lib/sentry-client.test.ts index 3b20cfca0..12fe0e291 100644 --- a/packages/cli/test/lib/sentry-client.test.ts +++ b/packages/cli/test/lib/sentry-client.test.ts @@ -3,6 +3,12 @@ * regression coverage. */ +import { + getActiveSpan, + SEMANTIC_ATTRIBUTE_SENTRY_OP, + spanToJSON, + startSpan, +} from "@sentry/node"; import { afterEach, beforeEach, describe, expect, test } from "vitest"; import { setAuthToken } from "../../src/lib/db/auth.js"; import { RateLimitError, TimeoutError } from "../../src/lib/errors.js"; @@ -12,6 +18,7 @@ import { getSdkConfig, resetAuthenticatedFetch, } from "../../src/lib/sentry-client.js"; +import { initSentry } from "../../src/lib/telemetry.js"; import { mockFetch, useTestConfigDir } from "../helpers.js"; useTestConfigDir("sentry-client-"); @@ -619,6 +626,49 @@ describe("user abort passthrough", () => { }); }); +describe("API request tracing", () => { + afterEach(() => { + initSentry(false); + }); + + async function tracedRequest(): Promise<{ + headers: Headers; + activeSpanOp: string | undefined; + }> { + let headers = new Headers(); + let activeSpanOp: string | undefined; + globalThis.fetch = mockFetch(async (input, init) => { + headers = new Headers( + init?.headers ?? (input instanceof Request ? input.headers : undefined), + ); + const span = getActiveSpan(); + activeSpanOp = span + ? spanToJSON(span).attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_OP] + : undefined; + return jsonResponse({ ok: true }); + }); + await startSpan({ name: "host", op: "test" }, () => + getAuthenticatedFetch()(`${REGION_URL}/api/0/organizations/`), + ); + return { headers, activeSpanOp }; + } + + test("adds its own http.client span and trace headers in library mode", async () => { + initSentry(true, { libraryMode: true }); + const { headers, activeSpanOp } = await tracedRequest(); + expect(headers.get("sentry-trace")).toBeTruthy(); + expect(headers.get("baggage")).toBeTruthy(); + expect(activeSpanOp).toBe("http.client"); + }); + + test("leaves HTTP spans and headers to the SDK integrations in CLI mode", async () => { + initSentry(true); + const { headers, activeSpanOp } = await tracedRequest(); + expect(headers.get("sentry-trace")).toBeNull(); + expect(activeSpanOp).toBe("test"); + }); +}); + describe("resolveTimeoutMs", () => { test("returns 120 000 ms for Seer /autofix/ POST paths", () => { expect( From 17dcf6305ba18e8e1c714f621fd988297473cb16 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 21:01:57 +0000 Subject: [PATCH 7/7] docs(cli): Update the measured @sentry/node load cost Co-Authored-By: Burak Yigit Kaya --- packages/cli/src/cli.ts | 4 ++-- packages/cli/src/lib/db/index.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 7a9dce272..937b355da 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -2,7 +2,7 @@ * CLI runner with fast-path dispatch. * * Shell completion (`__complete`) is dispatched before any heavy imports - * to avoid loading `@sentry/node` (~45ms in the bundle). All other commands go through + * to avoid loading `@sentry/node` (~100ms). All other commands go through * the full CLI with telemetry, middleware, and error recovery. * * Extracted from `bin.ts` so the logic is testable and reusable without @@ -75,7 +75,7 @@ async function preloadProjectContext(cwd: string): Promise { * completion engine and SQLite cache modules. */ export async function runCompletion(completionArgs: string[]): Promise { - // Disable telemetry so db/index.ts skips the @sentry/node lazy-require (~45ms) + // Disable telemetry so db/index.ts skips the @sentry/node lazy-require (~100ms) getEnv().SENTRY_CLI_NO_TELEMETRY = "1"; const { handleComplete } = await import("./lib/complete.js"); handleComplete(completionArgs); diff --git a/packages/cli/src/lib/db/index.ts b/packages/cli/src/lib/db/index.ts index 19c99939e..db8dcbe87 100644 --- a/packages/cli/src/lib/db/index.ts +++ b/packages/cli/src/lib/db/index.ts @@ -224,7 +224,7 @@ export function getDatabase(): Database { } // Wrap with tracing proxy for automatic query instrumentation. - // Lazy-require telemetry to avoid top-level import of @sentry/node (~45ms). + // Lazy-require telemetry to avoid top-level import of @sentry/node (~100ms). // Shell completions set SENTRY_CLI_NO_TELEMETRY=1 to skip this entirely. if (getEnv().SENTRY_CLI_NO_TELEMETRY === "1") { db = rawDb;