Skip to content

feat: Add strict trace continuation support#5136

Open
giortzisg wants to merge 32 commits intomainfrom
feat/strict-trace-continuation
Open

feat: Add strict trace continuation support#5136
giortzisg wants to merge 32 commits intomainfrom
feat/strict-trace-continuation

Conversation

@giortzisg
Copy link
Copy Markdown

Summary

  • Extract org ID from DSN host (e.g., o123.ingest.sentry.io"123")
  • Add strictTraceContinuation (boolean, default false) and orgId (String) options to SentryOptions
  • Propagate sentry-org_id in baggage / Dynamic Sampling Context
  • Validate incoming traces per the decision matrix (mismatched org → new trace; strict mode rejects missing org)

Decision Matrix

Baggage org SDK org strict=false strict=true
1 1 Continue Continue
None 1 Continue New trace
1 None Continue New trace
None None Continue Continue
1 2 New trace New trace

Changes

  • Dsn.java — org ID extraction from host using ^o(\d+)\. regex
  • SentryOptions.javastrictTraceContinuation, orgId, and getEffectiveOrgId()
  • Baggage.javaORG_ID DSCKey, getter/setter, population in setValuesFromTransaction/setValuesFromScope/fromEvent
  • PropagationContext.javashouldContinueTrace() logic in fromHeaders()
  • Scopes.java — pass options to fromHeaders()

Test plan

  • DSN org ID extraction tests (5 cases)
  • PropagationContext decision matrix tests (10 cases)

Closes #5128

🤖 Generated with Claude Code

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 2, 2026

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

  • Add strict trace continuation support by giortzisg in #5136
  • Sync file attachments to native by bitsandfoxes in #5211

Internal Changes 🔧

  • Add THIRD_PARTY_NOTICES.md for vendored third-party code by markushi in #5186

🤖 This preview updates automatically when you update the PR.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 2, 2026

Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against 070ee32

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 2, 2026

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 282.33 ms 364.08 ms 81.75 ms
Size 0 B 0 B 0 B

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
9ea89e8 308.06 ms 358.16 ms 50.10 ms
b193867 319.59 ms 403.09 ms 83.50 ms
dcc6bbf 382.58 ms 462.13 ms 79.54 ms
d501a7e 307.33 ms 341.94 ms 34.61 ms
22f4345 325.23 ms 454.66 ms 129.43 ms
33a08cc 267.08 ms 340.45 ms 73.37 ms
dba088c 321.78 ms 364.59 ms 42.82 ms
092f017 353.13 ms 433.84 ms 80.71 ms
d501a7e 314.55 ms 343.34 ms 28.79 ms
ce0a49e 532.00 ms 609.96 ms 77.96 ms

App size

Revision Plain With Sentry Diff
9ea89e8 1.58 MiB 2.28 MiB 716.23 KiB
b193867 1.58 MiB 2.19 MiB 620.00 KiB
dcc6bbf 1.58 MiB 2.12 MiB 553.10 KiB
d501a7e 0 B 0 B 0 B
22f4345 1.58 MiB 2.29 MiB 719.83 KiB
33a08cc 1.58 MiB 2.12 MiB 555.28 KiB
dba088c 1.58 MiB 2.13 MiB 558.99 KiB
092f017 0 B 0 B 0 B
d501a7e 0 B 0 B 0 B
ce0a49e 1.58 MiB 2.10 MiB 532.94 KiB

Previous results on branch: feat/strict-trace-continuation

Startup times

Revision Plain With Sentry Diff
455f13a 320.20 ms 367.52 ms 47.33 ms
7151c08 321.63 ms 391.84 ms 70.21 ms
9830aa7 325.45 ms 380.46 ms 55.01 ms
01b86b9 312.27 ms 360.76 ms 48.49 ms
f4b6f75 392.12 ms 440.69 ms 48.57 ms
e9fcd87 334.50 ms 351.63 ms 17.13 ms
79a9407 348.85 ms 428.10 ms 79.25 ms
5a6ea07 313.02 ms 366.33 ms 53.31 ms
bbab8da 309.58 ms 363.22 ms 53.64 ms

App size

Revision Plain With Sentry Diff
455f13a 0 B 0 B 0 B
7151c08 0 B 0 B 0 B
9830aa7 0 B 0 B 0 B
01b86b9 0 B 0 B 0 B
f4b6f75 0 B 0 B 0 B
e9fcd87 0 B 0 B 0 B
79a9407 1.58 MiB 2.29 MiB 723.70 KiB
5a6ea07 0 B 0 B 0 B
bbab8da 1.58 MiB 2.29 MiB 723.51 KiB

giortzisg and others added 6 commits March 4, 2026 14:01
Extract org ID from DSN host, add strictTraceContinuation and orgId
options, propagate sentry-org_id in baggage, and validate incoming
traces per the decision matrix.

Closes #5128
Add public API declarations for new org ID and strict trace
continuation methods on Baggage, PropagationContext, and SentryOptions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Make Dsn.orgId final, remove unnecessary setter
- Fix test signatures to use List<String> for baggage headers
- Add strictTraceContinuation and orgId to ExternalOptions and merge()
- Add options to ManifestMetadataReader for Android manifest config
- Use Sentry.getCurrentScopes().getOptions() in legacy fromHeaders overload
- Improve CHANGELOG description with details about new options
- Update API surface file for ExternalOptions changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add comment to empty catch block in PropagationContext to satisfy -Werror
- Add setOrgId setter to Dsn class (remove final modifier on orgId field) to
  support the existing test for org ID override

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@giortzisg giortzisg force-pushed the feat/strict-trace-continuation branch from 457e177 to e30064c Compare March 4, 2026 13:14
giortzisg and others added 5 commits March 4, 2026 14:20
…x OTel overload, add option tests, make Dsn.orgId final

- Remove PropagationContext.fromHeaders overload without SentryOptions; all
  callers now pass options (or null) explicitly instead of relying on
  Sentry.getCurrentScopes()
- Add SentryOptions parameter to the OTel-facing fromHeaders(SentryTraceHeader,
  Baggage, SpanId) overload so OpenTelemetry integrations also check orgId
- Make Dsn.orgId final and remove the setter — orgId is only set during
  DSN parsing in the constructor
- Add tests for strictTraceContinuation and orgId options in
  ExternalOptionsTest, SentryOptionsTest, and ManifestMetadataReaderTest
- Improve CHANGELOG entry with customer-facing description
- Update API declarations (apiDump)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sentry
Copy link
Copy Markdown

sentry bot commented Mar 9, 2026

Sentry Build Distribution

App Name App ID Version Configuration Install Page
SDK Size io.sentry.tests.size 8.37.1 (1) release Install Build

Copy link
Copy Markdown
Member

@adinauer adinauer left a comment

Choose a reason for hiding this comment

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

Please also change SentryAutoConfigurationTest.kt in all 3 spring boot modules.

I assume it's OK to not backfill org ID on a frozen baggage (i.e. incoming baggage without org ID).

- Remove duplicated org ID check in PropagationContext.fromHeaders, pass
  options through to the single-check overload instead
- Add debug log when trace is not continued in the SentryTraceHeader overload
- Handle empty/blank org ID strings in shouldContinueTrace to avoid
  silently breaking traces
- Update OtelSentrySpanProcessor to use PropagationContext.fromHeaders
  with options for org_id validation
- Rename ExternalOptions property key to enable-strict-trace-continuation
  (matching the enable- prefix convention for newer options)
- Update ExternalOptionsTest to use the new property key
- Add strict-trace-continuation and org-id properties to all 3 Spring
  Boot SentryAutoConfigurationTest modules
- Improve CHANGELOG entry with detailed customer-facing descriptions
  and configuration examples for all options

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sentry
Copy link
Copy Markdown

sentry bot commented Mar 11, 2026

Sentry Build Distribution

App Version Configuration
SDK Size 8.34.1 (1) release

adinauer and others added 6 commits March 20, 2026 12:06
Update the trace-continuation rejection log message to cover all strict org ID validation failures, including missing org IDs, not just mismatches.

Co-Authored-By: Claude <noreply@anthropic.com>
Rename the Android manifest option to io.sentry.strict-trace-continuation.enabled to align with existing enabled-style manifest flags.

Update changelog documentation to match the new Android key.

Co-Authored-By: Claude <noreply@anthropic.com>
Annotate SentryOptions.getEffectiveOrgId with ApiStatus.Internal since it is used as an internal helper for trace propagation org ID resolution.

Co-Authored-By: Claude <noreply@anthropic.com>
Move strict trace continuation org-id validation logic to TracingUtils
so it can be reused by tracing entry points.

Update PropagationContext to call the shared helper and add dedicated
TracingUtils tests for strict/non-strict org-id continuation outcomes.

Co-Authored-By: Claude <noreply@anthropic.com>
Apply strict trace continuation checks in all OpenTelemetry propagator
extract paths before creating remote parent span context.

When org-id validation fails, return the original context and ignore
incoming sentry-trace and baggage to keep propagation behavior aligned
with strict continuation requirements.

Add rejection tests for OtelSentryPropagator, deprecated
SentryPropagator, and OpenTelemetryOtlpPropagator.

Co-Authored-By: Claude <noreply@anthropic.com>
@adinauer adinauer marked this pull request as ready for review March 23, 2026 15:57
Copy link
Copy Markdown

@cursor cursor bot left a comment

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 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Empty/blank orgId bypasses DSN fallback silently
    • getEffectiveOrgId() now treats empty or whitespace-only configured org IDs as unset so DSN org ID fallback is used, and a unit test was added to cover empty/blank inputs.

Create PR

Or push these changes by commenting:

@cursor push 1b0f711f6d
Preview (1b0f711f6d)
diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java
--- a/sentry/src/main/java/io/sentry/SentryOptions.java
+++ b/sentry/src/main/java/io/sentry/SentryOptions.java
@@ -2337,7 +2337,7 @@
    */
   @ApiStatus.Internal
   public @Nullable String getEffectiveOrgId() {
-    if (orgId != null) {
+    if (orgId != null && !orgId.trim().isEmpty()) {
       return orgId;
     }
     try {

diff --git a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt
--- a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt
+++ b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt
@@ -1020,6 +1020,19 @@
   }
 
   @Test
+  fun `getEffectiveOrgId falls back to DSN org id when explicit orgId is empty or blank`() {
+    val emptyOrgIdOptions = SentryOptions()
+    emptyOrgIdOptions.dsn = "https://key@o123.ingest.sentry.io/456"
+    emptyOrgIdOptions.orgId = ""
+    assertEquals("123", emptyOrgIdOptions.effectiveOrgId)
+
+    val blankOrgIdOptions = SentryOptions()
+    blankOrgIdOptions.dsn = "https://key@o123.ingest.sentry.io/456"
+    blankOrgIdOptions.orgId = "   "
+    assertEquals("123", blankOrgIdOptions.effectiveOrgId)
+  }
+
+  @Test
   fun `getEffectiveOrgId falls back to DSN org id`() {
     val options = SentryOptions()
     options.dsn = "https://key@o123.ingest.sentry.io/456"

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

adinauer and others added 3 commits March 26, 2026 06:15
The getEffectiveOrgId() method only checked orgId != null, allowing
empty strings and whitespace-only values to bypass the DSN fallback
mechanism. This caused empty org IDs to propagate to outgoing baggage
headers as sentry-org_id=, silently breaking trace continuation in
strict mode.

Update getEffectiveOrgId() to trim the orgId value and check if it's
empty after trimming. Empty or blank values now correctly fall back
to the DSN org ID instead of propagating as empty strings.

Add comprehensive test coverage for all edge cases including empty
strings, whitespace-only values, and their impact on baggage
propagation and strict trace continuation.

Co-Authored-By: Claude <noreply@anthropic.com>
getEffectiveOrgId() already guarantees it returns either null or a
trimmed, non-empty string. The defensive trim and empty check in
shouldContinueTrace was dead code that could never change the result.

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@lbloder lbloder left a comment

Choose a reason for hiding this comment

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

LGTM 👍

Copy link
Copy Markdown

@cursor cursor bot left a comment

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 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Sampled flag silently changes from local to parent decision
    • When building propagation context from incoming headers we now explicitly overwrite its sampled value with the locally computed sampled decision so downstream propagation preserves local sampling behavior.

Create PR

Or push these changes by commenting:

@cursor push 9c18d161d0
Preview (9c18d161d0)
diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java
--- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java
+++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java
@@ -100,6 +100,7 @@
       if (sentryTraceHeader != null) {
         propagationContext =
             PropagationContext.fromHeaders(sentryTraceHeader, baggage, sentrySpanId, sentryOptions);
+        propagationContext.setSampled(sampled);
       } else {
         propagationContext =
             new PropagationContext(

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

…ssor

The propagator is the correct enforcement point for org ID validation.
By the time the span processor runs, OTel has already created the span
with the remote parent's trace ID. If shouldContinueTrace rejects here,
it creates a fresh PropagationContext with a mismatched trace ID rather
than cleanly rejecting the trace.

Co-Authored-By: Claude <noreply@anthropic.com>
adinauer and others added 2 commits March 30, 2026 14:19
SentryPropagatorTest calls Sentry.init with strict trace continuation
but never closes Sentry afterward. This leaks global state into
SentrySpanProcessorTest where SentryPropagator uses the global
ScopesAdapter and rejects incoming sentry-trace headers under the
leaked strict mode configuration.

Add @AfterTest that calls Sentry.close() to prevent state leakage.

Co-Authored-By: Claude <noreply@anthropic.com>
The strict continuation tests called Sentry.init with strict mode and
org ID configuration, which leaked global state into other tests.
Sentry.close() does not fully reset the global scope options, so
SentrySpanProcessorTest's SentryPropagator (using ScopesAdapter) would
reject incoming sentry-trace headers.

Use the package-private constructor with mock IScopes instead of
relying on global Sentry state for strict continuation validation.

Co-Authored-By: Claude <noreply@anthropic.com>
adinauer and others added 3 commits March 30, 2026 16:48
Sentry.init uses OtelContextScopesStorage which pushes scopes onto the
OTel context via makeCurrent(). The returned pop-token is discarded by
Sentry.init, and OtelContextScopesStorage.close() is a no-op, so
Sentry.close() alone does not restore the OTel context.

Since JUnit reuses the same thread across test classes, stale scopes
from SentryPropagatorTest (with strict continuation enabled) leaked
into SentrySpanProcessorTest via Context.current().

Add Context.root().makeCurrent() after Sentry.close() in all
propagator test teardowns to reset the thread-local OTel context.

Co-Authored-By: Claude <noreply@anthropic.com>
The previous commit replaced the invalid-span inject test with a
no-span-in-context test. These are distinct scenarios: an explicitly
invalid span vs no span at all. Restore the invalid span test and
keep both.

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Copy Markdown

@cursor cursor bot left a comment

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 1 potential issue.

Fix All in Cursor

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

.getLogger()
.log(SentryLevel.DEBUG, "Not continuing trace due to strict org ID validation failure.");
return new PropagationContext();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Duplicate org ID validation in propagator and fromHeaders

Low Severity

TracingUtils.shouldContinueTrace is called in both the OTel propagators and in PropagationContext.fromHeaders. The OTel propagator checks and returns early, then downstream code (e.g. SentrySampler, SentrySpanProcessor) calls fromHeaders which checks again. For the non-OTel path via Scopes.continueTrace, the check only happens once in fromHeaders, which is correct. The propagator-level check is redundant for the PropagationContext.fromHeaders callers—consider extracting the validation to only one layer to avoid future divergence risk.

Additional Locations (1)
Fix in Cursor Fix in Web

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.

Implement strict trace continuation (org_id validation)

4 participants