From d6d11fe39988e15f0e382622b22e777569547f82 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 31 Aug 2026 10:50:13 +0200 Subject: [PATCH] feat(core): Deprecate `forceTransaction` span start option `forceTransaction` no longer has a concrete use case: all spans are indexed and searchable in Sentry, so a span does not need to be a transaction to be queried, filtered or aggregated on. The option will be removed in the next major version. For the remaining cases where a span genuinely has to be a segment (root) span, the JSDoc points to starting it without a parent span (`withActiveSpan(null, ...)`, optionally inside `continueTrace`) instead of forcing it into a transaction. Internal SDK usages are suppressed with an oxlint directive for now and will be evaluated separately. Co-Authored-By: Claude Opus 5 (1M context) --- .../src/core/build-plugin-manager.ts | 2 ++ .../core/src/integrations/mcp-server/spans.ts | 1 + packages/core/src/tracing/trace.ts | 3 +++ packages/core/src/trpc.ts | 1 + packages/core/src/types/startSpanOptions.ts | 26 +++++++++++++++++++ packages/nestjs/src/integrations/helpers.ts | 1 + .../common/withServerActionInstrumentation.ts | 1 + 7 files changed, 35 insertions(+) diff --git a/packages/bundler-plugins/src/core/build-plugin-manager.ts b/packages/bundler-plugins/src/core/build-plugin-manager.ts index b7f5082063a3..242a77d73f0d 100644 --- a/packages/bundler-plugins/src/core/build-plugin-manager.ts +++ b/packages/bundler-plugins/src/core/build-plugin-manager.ts @@ -524,6 +524,7 @@ export function createSentryBuildPluginManager( Only use this if you need to manually inject debug IDs into the build artifacts. */ async injectDebugIds(buildArtifactPaths: string[]) { + // oxlint-disable-next-line typescript/no-deprecated await startSpan({ name: 'inject-debug-ids', scope: sentryScope, forceTransaction: true }, async () => { try { const cliInstance = new SentryCliAdapter(options); @@ -561,6 +562,7 @@ export function createSentryBuildPluginManager( await startSpan( // This is `forceTransaction`ed because this span is used in dashboards in the form of indexed transactions. + // oxlint-disable-next-line typescript/no-deprecated { name: 'debug-id-sourcemap-upload', scope: sentryScope, forceTransaction: true }, async () => { // If we're not using a temp folder, we must not prepare artifacts in-place (to avoid mutating user files) diff --git a/packages/core/src/integrations/mcp-server/spans.ts b/packages/core/src/integrations/mcp-server/spans.ts index 76b3ba4ffa3e..7277d199a06e 100644 --- a/packages/core/src/integrations/mcp-server/spans.ts +++ b/packages/core/src/integrations/mcp-server/spans.ts @@ -115,6 +115,7 @@ function createMcpSpan(config: McpSpanConfig): unknown { return startSpan( { name: spanName, + // oxlint-disable-next-line typescript/no-deprecated forceTransaction: true, attributes, }, diff --git a/packages/core/src/tracing/trace.ts b/packages/core/src/tracing/trace.ts index b52d62624f23..b7ac40595830 100644 --- a/packages/core/src/tracing/trace.ts +++ b/packages/core/src/tracing/trace.ts @@ -52,6 +52,7 @@ import { SUPPRESS_TRACING_KEY } from './constants'; */ export function startSpan(options: StartSpanOptions, callback: (span: Span) => T): T { const spanArguments = parseSentrySpanArguments(options); + // oxlint-disable-next-line typescript/no-deprecated const { forceTransaction, parentSpan: customParentSpan, scope: customScope } = options; // We still need to fork a potentially passed scope, as we set the active span on it @@ -104,6 +105,7 @@ export function startSpan(options: StartSpanOptions, callback: (span: Span) = */ export function startSpanManual(options: StartSpanOptions, callback: (span: Span, finish: () => void) => T): T { const spanArguments = parseSentrySpanArguments(options); + // oxlint-disable-next-line typescript/no-deprecated const { forceTransaction, parentSpan: customParentSpan, scope: customScope } = options; const customForkedScope = customScope?.clone(); @@ -150,6 +152,7 @@ export function startSpanManual(options: StartSpanOptions, callback: (span: S */ export function startInactiveSpan(options: StartSpanOptions): Span { const spanArguments = parseSentrySpanArguments(options); + // oxlint-disable-next-line typescript/no-deprecated const { forceTransaction, parentSpan: customParentSpan, scope: customScope } = options; // If `options.scope` is defined, we use this as as a wrapper, diff --git a/packages/core/src/trpc.ts b/packages/core/src/trpc.ts index e08248b3bbe4..55e428f60458 100644 --- a/packages/core/src/trpc.ts +++ b/packages/core/src/trpc.ts @@ -103,6 +103,7 @@ export function trpcMiddleware(options: SentryTrpcMiddlewareOptions = {}) { [TRPC_PROCEDURE_PATH]: String(path), [TRPC_PROCEDURE_TYPE]: String(type), }, + // oxlint-disable-next-line typescript/no-deprecated forceTransaction: !!options.forceTransaction, }, async span => { diff --git a/packages/core/src/types/startSpanOptions.ts b/packages/core/src/types/startSpanOptions.ts index 9499768836b3..39d533f96cf6 100644 --- a/packages/core/src/types/startSpanOptions.ts +++ b/packages/core/src/types/startSpanOptions.ts @@ -39,6 +39,32 @@ export interface StartSpanOptions { * If set to true, this span will be forced to be treated as a transaction in the Sentry UI, if possible and applicable. * Note that it is up to the SDK to decide how exactly the span will be sent, which may change in future SDK versions. * It is not guaranteed that a span started with this flag set to `true` will be sent as a transaction. + * + * @deprecated This option will be removed in the next major version of the SDK. There is no longer a concrete use + * case for it: all spans are indexed and searchable in Sentry, so a span no longer needs to be a transaction to be + * queried, filtered or aggregated on. In most cases, simply drop the option. The span is still sent, just as a child + * of its parent span, if a parent span is active. + * If you do need the span to be a segment (root) span, follow the examples below:. + * + * @example Making a span a root span: + * ```js + * Sentry.withActiveSpan(null, () => { + * Sentry.startSpan({ name: 'span-that-should-be-a-root' }, () => { + * // ... + * }); + * }); + * ``` + * + * @example Keeping the root span attached to a specific trace: + * ```js + * Sentry.continueTrace({ sentryTrace, baggage }, () => + * Sentry.withActiveSpan(null, () => + * Sentry.startSpan({ name: 'span-that-should-be-a-root' }, () => { + * // ... + * }), + * ), + * ); + * ``` */ forceTransaction?: boolean; diff --git a/packages/nestjs/src/integrations/helpers.ts b/packages/nestjs/src/integrations/helpers.ts index 655f53c6b53b..cfbdc1b2dd61 100644 --- a/packages/nestjs/src/integrations/helpers.ts +++ b/packages/nestjs/src/integrations/helpers.ts @@ -117,6 +117,7 @@ export function getEventSpanOptions(event: string): { [SENTRY_OP]: FUNCTION, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.event.nestjs', }, + // oxlint-disable-next-line typescript/no-deprecated forceTransaction: true, }; } diff --git a/packages/nextjs/src/common/withServerActionInstrumentation.ts b/packages/nextjs/src/common/withServerActionInstrumentation.ts index aa02017e38c4..fb32aaf6ca90 100644 --- a/packages/nextjs/src/common/withServerActionInstrumentation.ts +++ b/packages/nextjs/src/common/withServerActionInstrumentation.ts @@ -118,6 +118,7 @@ async function withServerActionInstrumentationImplementation