Skip to content

feat(core): Add __SENTRY_EXCLUDE_STATIC_LIFECYCLE__ flag to tree-shake the static span lifecycle - #26144

Open
sentry-junior[bot] wants to merge 5 commits into
developfrom
feat/exclude-transactions-flag
Open

sentry-junior[bot] wants to merge 5 commits into
developfrom
feat/exclude-transactions-flag

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Why: Span streaming is the default in v11, but a streaming app still ships the code that builds and processes transaction events (_convertSpanToTransaction, the transaction branch of processBeforeSend, gen_ai extraction, transaction-event conversion helpers, the web vitals measurements branch). Runtime hasSpanStreamingEnabled(client) / traceLifecycle checks keep it alive. __SENTRY_TRACING__: false (#22835) only helps apps that don't trace at all.

This adds an opt-in build flag. Replacing __SENTRY_EXCLUDE_STATIC_LIFECYCLE__ with true means the SDK always streams spans: traceLifecycle: 'static' is ignored (normalized to 'stream', and server clients still add spanStreamingIntegration), and the transaction code paths become dead code. Segments are still sent as span envelopes. When the flag isn't defined, behavior is unchanged.

The guard sits inline at each call site, like __SENTRY_TRACING__. A per-package module constant (like DEBUG_BUILD) doesn't work. Webpack still drops the branches with it, but with the flag set, esbuild 0.27 kept them and the whole saving was lost (131,164 vs 127,648 B). CDN bundles replace the flag with false at build time, so the guards cost them nothing.

Size (esbuild 0.27, minify, ESM, browser, __SENTRY_DEBUG__: false; BrowserClient + browserTracingIntegration, tracesSampleRate: 1, setCurrentClient, client.init()):

raw gzip brotli-11
develop 131,185 45,437 40,286
this PR, flag unset 131,791 (+606) 45,513 (+76) 40,331 (+45)
this PR, flag true 127,648 (−3,537) 44,358 (−1,079) 39,362 (−924)

With webpack via size-limit (init + browserTracingIntegration), the flag saves 1,405 B gzip (49,304 → 47,899). The new size-limit entry tracks it.

I ran an end-to-end check against the built packages: with the flag, a startSpan segment produces a span envelope item, including when traceLifecycle: 'static' is passed. Without the flag, behavior is unchanged (span for stream, transaction for static).

Left in:

  • Transaction bookkeeping in _processEvent (dropped-span outcomes, transaction_info source), which is small and still handles manually captured type: 'transaction' events
  • maybeWarnAboutIgnoredTransactionOptions
  • measurements in getSpanJSON/static span JSON
  • the hasSpanStreamingEnabled(client) ? … : … ternaries in integrations (span names, attribute keys), plus static branches in browser-utils web-vitals/spans.ts and interactions.ts
  • standalone span machinery
  • the Cloudflare client's own traceLifecycle !== 'static' check
  • a bundleSizeOptimizations.excludeStaticLifecycle option in the bundler plugins (left for a follow-up)

How to review: Start with sentrySpan.ts and client.ts. excludeStaticLifecycle.test.ts covers the flag semantics.

Unsure about:

  • The flag name. It was __SENTRY_EXCLUDE_TRANSACTIONS__ at first. It's now __SENTRY_EXCLUDE_STATIC_LIFECYCLE__, because it removes traceLifecycle: 'static', and it follows the __SENTRY_EXCLUDE_*__ convention.
  • npm builds that don't define the flag pay for the inline guards: +60 B gzip for @sentry/browser and +117 B for @sentry/browser (incl. Tracing) in size-limit. CDN bundles are within ±3 B of develop.

Refs #22580

via @BYK.

--

View Session in Sentry

…transaction code

With span streaming, the code that builds and processes transaction events is dead
but still ships. Replacing `__SENTRY_EXCLUDE_TRANSACTIONS__` with `true` makes the SDK
always stream spans and lets bundlers drop those paths. Defaults are unchanged.

Refs #22580

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
@sentry-junior
sentry-junior Bot requested review from a team as code owners November 5, 2026 13:30
@sentry-junior
sentry-junior Bot requested a review from a team as a code owner November 5, 2026 13:30
@sentry-junior
sentry-junior Bot requested review from Lms24, isaacs, logaretm, mydea and s1gr1d and removed request for a team November 5, 2026 13:30

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e6aa1e9. Configure here.

@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

size-limit report 📦

Compared: 4f047bc (PR test merge using develop at 83f29b7)

Against: 83f29b7 (develop) · base run #65541

✅ Baseline matches: this diff contains only this PR's changes.

Path Size % Change Change
@sentry/browser 30.05 kB +0.43% +127 B 🔺
@sentry/browser - with treeshaking flags 28.21 kB +0.48% +133 B 🔺
@sentry/browser - with treeshaking flags tracing without tracing 28.11 kB +0.49% +136 B 🔺
@sentry/browser (incl. Tracing) 52.5 kB +0.4% +209 B 🔺
@sentry/browser (incl. Tracing + Span Streaming) 52.51 kB +0.4% +208 B 🔺
@sentry/browser (incl. Tracing, Profiling) 55.47 kB +0.36% +194 B 🔺
@sentry/browser (incl. Tracing, Replay) 92.91 kB +0.23% +209 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 81.56 kB +0.24% +192 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas) 97.64 kB +0.22% +213 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback) 110.63 kB +0.19% +207 B 🔺
@sentry/browser (incl. Feedback) 47.64 kB +0.26% +119 B 🔺
@sentry/browser (incl. sendFeedback) 35.14 kB +0.37% +128 B 🔺
@sentry/browser (incl. FeedbackAsync) 40.27 kB +0.33% +132 B 🔺
@sentry/browser (incl. Metrics) 31.61 kB +0.42% +130 B 🔺
@sentry/browser (incl. Logs) 31.9 kB +0.41% +130 B 🔺
@sentry/browser (incl. Metrics & Logs) 32.59 kB +0.41% +130 B 🔺
@sentry/react 31.91 kB +0.42% +133 B 🔺
@sentry/react (incl. Tracing) 54.98 kB +0.41% +220 B 🔺
@sentry/vue 38.52 kB +0.49% +187 B 🔺
@sentry/vue (incl. Tracing) 55.59 kB +0.46% +252 B 🔺
@sentry/svelte 30.08 kB +0.43% +128 B 🔺
@sentry/remix (Remix 3 client bundle) 58.12 kB +0.35% +199 B 🔺
CDN Bundle 31.72 kB -0.01% -2 B 🔽
CDN Bundle (incl. Tracing) 52.89 kB -0.01% -1 B 🔽
CDN Bundle (incl. Logs, Metrics) 34.52 kB +0.01% +1 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) 55.39 kB +0.01% +1 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) 76 kB -0.01% -2 B 🔽
CDN Bundle (incl. Tracing, Replay) 91.17 kB +0.01% +2 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 93.72 kB +0.01% +2 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) 97.29 kB -0.01% -1 B 🔽
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 99.89 kB +0.01% +2 B 🔺
CDN Bundle - uncompressed 92.76 kB - -
CDN Bundle (incl. Tracing) - uncompressed 156.02 kB +0.01% +3 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed 101.29 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 163.8 kB +0.01% +3 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 233.18 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 278.04 kB +0.01% +3 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 285.81 kB +0.01% +3 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 291.75 kB +0.01% +3 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 299.5 kB +0.01% +3 B 🔺
@sentry/nextjs (client) 57.94 kB +0.36% +207 B 🔺
@sentry/sveltekit (client) 53.02 kB +0.38% +200 B 🔺
@sentry/core/server 44.42 kB +0.31% +137 B 🔺
@sentry/core/browser 14.08 kB +0.41% +57 B 🔺
@sentry/node 157.66 kB +0.17% +265 B 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection) 82.28 kB - -
@sentry/node - without tracing 97.32 kB +0.18% +171 B 🔺
@sentry/node - without channel injection 137.93 kB +0.19% +248 B 🔺
@sentry/aws-serverless 106.06 kB +0.22% +223 B 🔺
@sentry/cloudflare (withSentry) - minified 204.57 kB +0.55% +1.1 kB 🔺
@sentry/cloudflare (withSentry) 506.92 kB +0.26% +1.28 kB 🔺
@sentry/nextjs/cloudflare (withSentry) - minified 226.29 kB +0.49% +1.1 kB 🔺
@sentry/browser (incl. Tracing) - with treeshaking flags excluding static lifecycle 47.59 kB added added

View base workflow run

…ns are excluded

With `__SENTRY_EXCLUDE_TRANSACTIONS__`, `traceLifecycle: 'static'` is ignored, so the
Cloudflare client must still add its own `spanStreamingIntegration` with
`flushOnSegmentEnd: false` instead of inheriting the default one from
`ServerRuntimeClient`. Also use the conventions constants in the web vitals test.
@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Optional E2E tests

Some optional checks were not successful. They do not block the merge. 8 not successful, 49 successful. See the run.

Check Status
❌ CI: Optional E2E Tests / E2E nextjs-16-cacheComponents (cloudflare) Test (optional) Failing after 10m Details
❌ CI: Optional E2E Tests / E2E cloudflare-pi-durable (latest) Test (optional) Failing after 35s Details
❌ CI: Optional E2E Tests / E2E cloudflare-flue-send-to-sentry Test (optional) Failing after 2m Details
❌ CI: Optional E2E Tests / E2E node-eve (latest) Test (optional) Failing after 58s Details
❌ CI: Optional E2E Tests / E2E node-eve Test (optional) Failing after 2m Details
❌ CI: Optional E2E Tests / E2E solidstart-2 Test (optional) Failing after 56s Details
❌ CI: Optional E2E Tests / E2E node-eve (orchestrion) Test (optional) Failing after 2m Details
❌ CI: Optional E2E Tests / E2E node-flue (latest) Test (optional) Failing after 39s Details
Successful checks
Check Status
✅ CI: Optional E2E Tests / E2E cloudflare-mastra Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E astro-7 Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E cloudflare-think (ai v6) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E nextjs-15 (latest) Test (optional) Successful in 3m Details
✅ CI: Optional E2E Tests / E2E gen-ai-libraries Test (optional) Successful in 49s Details
✅ CI: Optional E2E Tests / E2E cloudflare-agent Test (optional) Successful in 47s Details
✅ CI: Optional E2E Tests / E2E nextjs-16 (vinext) Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E cloudflare-workers-send-to-sentry Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E cloudflare-pi-durable Test (optional) Successful in 46s Details
✅ CI: Optional E2E Tests / E2E gen-ai-libraries (latest) Test (optional) Successful in 49s Details
✅ CI: Optional E2E Tests / E2E nestjs-12 (latest) Test (optional) Successful in 55s Details
✅ CI: Optional E2E Tests / E2E node-fastify-6 Test (optional) Successful in 36s Details
✅ CI: Optional E2E Tests / E2E nuxt-5-cloudflare Test (optional) Successful in 48s Details
✅ CI: Optional E2E Tests / E2E deno (latest) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E astro-7-static Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E cloudflare-think Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E debug-id-sourcemaps Test (optional) Successful in 56s Details
✅ CI: Optional E2E Tests / E2E node-express-mcp-v1 Test (optional) Successful in 33s Details
✅ CI: Optional E2E Tests / E2E node-express-send-to-sentry-static Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E node-mastra Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E gen-ai-libraries (cloudflare) Test (optional) Successful in 41s Details
✅ CI: Optional E2E Tests / E2E nuxt-5 Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E nextjs-14 (latest) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E nuxt-5-sourcemaps (client source maps kept) Test (optional) Successful in 49s Details
✅ CI: Optional E2E Tests / E2E node-express-mcp-v2 Test (optional) Successful in 36s Details
✅ CI: Optional E2E Tests / E2E nextjs-15 (turbo) Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E tanstackstart-react-sourcemaps Test (optional) Successful in 38s Details
✅ CI: Optional E2E Tests / E2E nuxt-5-sourcemaps (static / nuxt generate) Test (optional) Successful in 42s Details
✅ CI: Optional E2E Tests / E2E react-send-to-sentry Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E node-pi-durable (latest) Test (optional) Successful in 44s Details
✅ CI: Optional E2E Tests / E2E nuxt-5-sourcemaps Test (optional) Successful in 41s Details
✅ CI: Optional E2E Tests / E2E vue-3 (no Options API) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E node-openai-decisions Test (optional) Successful in 30s Details
✅ CI: Optional E2E Tests / E2E node-mastra (latest) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E node-express-send-to-sentry Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E tanstackstart-react (latest) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E nextjs-16-tunnel (cloudflare) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E nuxt-5 (tunnel-dev) Test (optional) Successful in 59s Details
✅ CI: Optional E2E Tests / E2E tanstackstart-react-cloudflare Test (optional) Successful in 42s Details
✅ CI: Optional E2E Tests / E2E react-router-7-framework-instrumentation Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E supabase-nextjs Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E node-flue Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E nextjs-16 (cloudflare, latest) Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E node-pi-durable Test (optional) Successful in 49s Details
✅ CI: Optional E2E Tests / E2E node-fastify-6 (tracer provider) Test (optional) Successful in 40s Details
✅ CI: Optional E2E Tests / E2E node-openai-decisions (latest) Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E nextjs-16 (deno) Test (optional) Successful in 2m Details
✅ CI: Optional E2E Tests / E2E nextjs-orpc Test (optional) Successful in 1m Details
✅ CI: Optional E2E Tests / E2E nextjs-16 (vinext, bun) Test (optional) Successful in 2m Details

@timfish

timfish commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

Why does this PR result in larger bundle sizes when this flag isn't set? 🤔

This is expected for the npm output. The flags remain so users can choose what they want to filter, but this shouldn't be impacting our CDN bundles.

@sentry-junior

sentry-junior Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

The extra bytes come from the guards themselves. The browser path has 7 inline guards: 3 in client.ts, 3 in sentrySpan.ts and 1 in web-vitals tracking.ts. When the flag isn't defined, each one minifies to typeof __SENTRY_EXCLUDE_TRANSACTIONS__=="boolean"&&__SENTRY_EXCLUDE_TRANSACTIONS__. The name is a free global, so minifiers can't rename it, and they can't drop the check because the value isn't known at build time. I measured about 84–90 B raw per site.

I replaced all 7 guards with false, which is what a build-time replacement would do, and measured BrowserClient + browserTracingIntegration:

raw gzip
esbuild 0.27 −603 B −77 B
webpack + terser −609 B −66 B
size-limit @sentry/browser −60 B (CI: +60)
size-limit @sentry/browser (incl. Tracing) −116 B (CI: +117)

So the guards account for basically the whole CI delta, and the raw numbers line up with the +612 B on the uncompressed CDN tracing bundles. __SENTRY_TRACING__ uses the same inline pattern. The CDN build only replaces __SENTRY_DEBUG__ and __SENTRY_BROWSER_BUNDLE__ (dev-packages/rollup-utils/plugins/bundlePlugins.mjs), so CDN bundles currently ship __SENTRY_TRACING__ checks unreplaced as well.

Cheaper guard forms I tried:

  • typeof … !== 'undefined' && … still drops all the dead code when the flag is set, but saves almost nothing: 49 B raw / 8 B gzip on esbuild, 8 B in the size-limit tracing entry, and 3 B more in the plain @sentry/browser entry.
  • A per-package module constant, like DEBUG_BUILD. This cuts the unset cost by about 400 B raw on webpack and webpack still removes the branches. esbuild 0.27 doesn't inline it, though, so an esbuild build with the flag set loses the whole saving (131,164 vs 127,648 B). That's why the guards are inline.

How much of the flag's saving each site carries (esbuild raw, flag set):

  • the transaction branch in processBeforeSend: 1,322 B
  • _convertSpanToTransaction: 1,145 B
  • the static beforeSendSpan check in sendStandaloneSpan: 604 B
  • extractGenAiSpansFromEvent: 558 B
  • the other 3 sites together: 222 B. Those 3 force the streaming path rather than shrinking code.

Ways to cut the default cost:

  1. Replace the flag with false in the CDN bundle build, the way __SENTRY_DEBUG__ is handled. This removes the cost for CDN bundles entirely. npm consumers who don't define the flag would still pay it.
  2. Reduce the number of guard sites. The 4 large sites carry about 3.6 KB of the 4.1 KB saving. The 3 semantic sites cost about 255 B raw unset, and could maybe be collapsed into the single traceLifecycle override.

I'm happy to do either one, or both. Just say which you'd prefer.

@timfish

timfish commented Nov 6, 2026

Copy link
Copy Markdown
Collaborator

Replace the flag with false in the CDN bundle build

This is the correct route!

…lse` in CDN bundles

CDN bundles always keep transaction support, so the unresolved runtime
checks for the flag only added bytes there.
@sentry-junior

sentry-junior Bot commented Nov 7, 2026

Copy link
Copy Markdown
Contributor Author

@timfish done in 479b891. The bundle-only replace plugin (makeBrowserBuildPlugin in dev-packages/rollup-utils/plugins/bundlePlugins.mjs) now sets __SENTRY_EXCLUDE_TRANSACTIONS__ to false. That's the same runtime behaviour as leaving the flag undefined, so CDN bundles work exactly as before. The checks just get resolved at build time.

Local build:bundle sizes, compared with the previous head (dc60544):

Bundle raw gzip -9
bundle.min.js −261 B −53 B
bundle.logs.metrics.min.js −261 B −53 B
bundle.tracing.min.js −609 B −132 B
bundle.tracing.replay.min.js −609 B −136 B
bundle.tracing.replay.feedback.logs.metrics.min.js −609 B −116 B

None of the 54 built .min.js files contains the flag any more; before this change, 28 did. The latest size-limit report agrees: the CDN rows are now between −1 B and +3 B against develop, compared with +261 B / +612 B uncompressed before. npm consumers who don't define the flag still pay the inline-guard cost (+60 B gzip for @sentry/browser, +117 B for @sentry/browser (incl. Tracing)).

Comment thread packages/core/src/client.ts Resolved
The flag removes `traceLifecycle: 'static'`, not transactions as a
concept, and the new name matches `__SENTRY_EXCLUDE_REPLAY_WORKER__`.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
@sentry-junior sentry-junior Bot changed the title feat(core): Add __SENTRY_EXCLUDE_TRANSACTIONS__ flag to tree-shake transaction code feat(core): Add __SENTRY_EXCLUDE_STATIC_LIFECYCLE__ flag to tree-shake the static span lifecycle Nov 9, 2026

@isaacs isaacs left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably we should add node's _INTERNAL_setDeferSegmentSpanCapture (packages/node/src/sdk/client.ts line 87) to the "left in" list, because Node's bundle size isn't the main priority here.

Also, there's no way to actually set this yet, for most users, right? Probably we should either add the bundler-plugins fix as part of this, or at least have an issue that tracks it so we don't forget to follow up with one.

Some comments below.

Comment thread packages/core/src/client.ts Outdated
}

if (isTransactionEvent(processedEvent)) {
if (

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The guard wraps the whole isTransactionEvent block, which includes the beforeSendTransaction call (line 1869). But transaction events still reach this code: a user can call captureEvent({ type: 'transaction', ... }), and the PR body says _processEvent keeps its transaction bookkeeping on purpose to handle them. Without the flag, in stream mode, nothing stops these events from going through beforeSendTransaction, ignoreSpans, and a static beforeSendSpan (I checked _processEvent, lines 1560-1612: there is no lifecycle check before processBeforeSend). With the flag, the same event is sent with none of these hooks run. beforeSendTransaction is often where users scrub PII, so the flag turns a scrubbed event into an unscrubbed one. The new test does not run transaction processing on transaction events asserts this as intended behavior.

So the flag changes runtime behavior beyond "drop dead code": stream mode without the flag and stream mode with the flag treat the same event in different ways.

Suggestion: pick one of these and make it explicit.

  • Drop transaction events outright under the flag (for example, in _processEvent, with a debug log and a dropped-event outcome). This matches "the SDK can only stream spans" and removes more code.
  • Or move the guard so it only covers the span-processing part (lines 1817-1867), and keep the beforeSendTransaction call. That keeps behavior identical to stream mode without the flag, and still drops the 1,322 B measured for this site (the beforeSendTransaction call is small).

Either way, change the test so it asserts the chosen behavior, not "the hook is not called but the event is sent".

attachStacktrace: true,
...options,
traceLifecycle: options.traceLifecycle === 'static' ? 'static' : 'stream',
traceLifecycle:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A user with traceLifecycle: 'static' and beforeSendSpan: Sentry.withStaticSpan(scrub) gets the lifecycle forced to 'stream'. Then captureSpan (packages/core/src/tracing/spans/captureSpan.ts line 84) skips the static callback, so scrub never runs. The only warning for this mismatch (packages/core/src/client.ts lines 296-308) is gated on DEBUG_BUILD, and people who set this flag will usually also set __SENTRY_DEBUG__: false (the new size-limit entry does). So the scrubber stops running with no message at all.

The other warning, maybeWarnAboutIgnoredTransactionOptions, does fire (it is not debug-gated), but it tells the user to "set traceLifecycle: 'static'", which cannot work under the flag.

Suggestion: when options.traceLifecycle === 'static' and the flag is set, emit one non-debug console.warn from the constructor (inside the flag-true branch, so it costs nothing in builds without the flag). Something like "traceLifecycle: 'static' is not available in this build (__SENTRY_EXCLUDE_STATIC_LIFECYCLE__). beforeSendTransaction, ignoreTransactions and withStaticSpan callbacks are ignored." Then the warning in packages/core/src/utils/warnAboutIgnoredTransactionOptions.ts does not need to change.

Comment thread packages/core/src/tracing/sentrySpan.ts Resolved
Comment thread packages/core/src/tracing/sentrySpan.ts Resolved
Comment thread packages/core/src/client.ts Resolved
// Extract gen_ai spans from transaction and convert to span v2 format.
// This mutates event.spans to remove the extracted spans.
const genAiSpanItem = extractGenAiSpansFromEvent(event, this);
const genAiSpanItem =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low: gen_ai extraction is skipped for manual transaction events.

Same root as the comment on packages/core/src/client.ts line 1812, but smaller: a manually captured transaction event with gen_ai spans keeps them inline instead of having them split into a v2 span item. This is probably fine, but it is a behavior change for the same input. If we resolve the other comment by dropping transaction events under the flag, this goes away.

… lifecycle

With `__SENTRY_EXCLUDE_STATIC_LIFECYCLE__`, manually captured transaction
events skipped `beforeSendTransaction`, so a PII scrubber could stop
running. Only the span processing is skipped now. Setting
`traceLifecycle: 'static'` under the flag also logs a non-debug warning,
because the static callbacks would otherwise stop running silently.
@sentry-junior
sentry-junior Bot requested a review from a team November 10, 2026 14:35
@logaretm

Copy link
Copy Markdown
Member

Doesn't seem to be worth it. weighing it up, every npm user pays +60/+117 B and with no way to set the flag without build tools. Dropping transactions is already planned tho, so this dead transaction weight won't be be lingering for long.

@sentry-junior

sentry-junior Bot commented Nov 10, 2026

Copy link
Copy Markdown
Contributor Author

Dropping transactions is already planned tho, so this dead transaction weight won't be be lingering for long.

@logaretm how long do you expect the transaction removal to take?

If it's temporary, the default cost is temporary too. Until then, apps that set the flag save a notable amount (about 1 KB gzip with esbuild, 1.4 KB with webpack) and have less code to parse at startup. Everyone else pays around 100 bytes, and CDN bundles pay nothing because they replace the flag with false.

The current default cost: size-limit showed +60 B gzip for @sentry/browser and +117 B for @sentry/browser (incl. Tracing) before d83094c. In my local size-limit runs, d83094c adds about 75 B more, mostly from the new non-debug console.warn. I can drop that warning to bring it back to about +60/+117 B.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants