Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
dc8d319
feat: Add strict trace continuation support
giortzisg Mar 2, 2026
e550ae3
Format code
getsentry-bot Mar 2, 2026
c272ee4
Add changelog entry
giortzisg Mar 2, 2026
1cfc2ea
Update API surface file for strict trace continuation
giortzisg Mar 3, 2026
108eb2d
Address review comments for strict trace continuation
giortzisg Mar 4, 2026
e30064c
Fix compilation errors after rebase on main
giortzisg Mar 4, 2026
4545735
fix: Move changelog entry to Unreleased section
giortzisg Mar 4, 2026
61ada6a
Format code
getsentry-bot Mar 4, 2026
519f6a6
fix: Address review comments — pass options to PropagationContext, fi…
giortzisg Mar 9, 2026
360fc94
fix: Add missing 8.34.1 changelog section
giortzisg Mar 9, 2026
ea9f456
merge: Resolve changelog conflict with main
giortzisg Mar 9, 2026
58bfc8e
Format code
getsentry-bot Mar 9, 2026
ebe8c4c
fix: Address PR review comments for strict trace continuation
giortzisg Mar 11, 2026
824b30b
Format code
getsentry-bot Mar 11, 2026
e2fdc46
fix(tracing): Clarify strict org validation debug log
adinauer Mar 20, 2026
d3b073d
fix(android): Use enabled suffix for strict trace manifest key
adinauer Mar 20, 2026
f7fef22
fix(api): Mark effective org ID helper as internal
adinauer Mar 20, 2026
71562fa
ref(tracing): Extract trace continuation decision into TracingUtils
adinauer Mar 23, 2026
327d897
fix(opentelemetry): Enforce strict continuation in propagators
adinauer Mar 23, 2026
f1edc98
Merge branch 'main' into feat/strict-trace-continuation
adinauer Mar 23, 2026
863f05b
Format code
getsentry-bot Mar 23, 2026
1d8e301
fix(tracing): Fix empty orgId bypassing DSN fallback
adinauer Mar 26, 2026
25e71db
Merge branch 'main' into feat/strict-trace-continuation
adinauer Mar 26, 2026
a3c86e7
Format code
getsentry-bot Mar 26, 2026
3d1d119
ref: Remove redundant trim of already-trimmed effective org ID
adinauer Mar 27, 2026
bad469f
Merge branch 'main' into feat/strict-trace-continuation
adinauer Mar 30, 2026
cf7d9fe
ref(tracing): Revert shouldContinueTrace check in OtelSentrySpanProce…
adinauer Mar 30, 2026
435d2e7
fix(test): Clean up global Sentry state in SentryPropagatorTest
adinauer Mar 30, 2026
63222e5
fix(test): Use mock scopes in propagator strict continuation tests
adinauer Mar 30, 2026
a4cd81a
fix(test): Reset OTel context in propagator test teardown
adinauer Mar 30, 2026
f029100
Merge branch 'main' into feat/strict-trace-continuation
adinauer Mar 30, 2026
070ee32
fix(test): Add back test for inject with invalid span
adinauer Mar 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix(tracing): Fix empty orgId bypassing DSN fallback
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>
  • Loading branch information
adinauer and claude committed Mar 26, 2026
commit 1d8e3016d9c4587d4e2a8ca604822d5359db2a39
6 changes: 5 additions & 1 deletion sentry/src/main/java/io/sentry/SentryOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -2334,11 +2334,15 @@ public void setOrgId(final @Nullable String orgId) {

/**
* Returns the effective org ID, preferring the explicit config option over the DSN-parsed value.
* Empty or whitespace-only explicit org IDs are treated as unset and fall back to the DSN.
*/
@ApiStatus.Internal
public @Nullable String getEffectiveOrgId() {
if (orgId != null) {
return orgId;
final @NotNull String trimmed = orgId.trim();
if (!trimmed.isEmpty()) {
return trimmed;
}
}
try {
final @Nullable String dsnOrgId = retrieveParsedDsn().getOrgId();
Expand Down
85 changes: 85 additions & 0 deletions sentry/src/test/java/io/sentry/BaggageTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.github.javafaker.Faker
import io.sentry.Baggage.MAX_BAGGAGE_LIST_MEMBER_COUNT
import io.sentry.Baggage.MAX_BAGGAGE_STRING_LENGTH
import io.sentry.protocol.SentryId
import io.sentry.protocol.TransactionNameSource
import java.util.UUID
import kotlin.test.BeforeTest
import kotlin.test.Test
Expand Down Expand Up @@ -736,6 +737,90 @@ class BaggageTest {
assertNull(baggage.sampleRate)
}

@Test
fun `setValuesFromScope falls back to DSN org id when explicit orgId is empty`() {
val options =
SentryOptions().apply {
dsn = "https://key@o123.ingest.sentry.io/456"
orgId = ""
}
val scope = Scope(options)
val baggage = Baggage(logger)

baggage.setValuesFromScope(scope, options)

assertEquals("123", baggage.orgId)
}

@Test
fun `setValuesFromScope falls back to DSN org id when explicit orgId is whitespace`() {
val options =
SentryOptions().apply {
dsn = "https://key@o123.ingest.sentry.io/456"
orgId = " "
}
val scope = Scope(options)
val baggage = Baggage(logger)

baggage.setValuesFromScope(scope, options)

assertEquals("123", baggage.orgId)
}

@Test
fun `setValuesFromTransaction falls back to DSN org id when explicit orgId is empty`() {
val options =
SentryOptions().apply {
dsn = "https://key@o123.ingest.sentry.io/456"
orgId = ""
}
val baggage = Baggage(logger)

baggage.setValuesFromTransaction(
SentryId(),
SentryId(),
options,
TracesSamplingDecision(true, 1.0),
"test-transaction",
TransactionNameSource.CUSTOM,
)

assertEquals("123", baggage.orgId)
}

@Test
fun `fromEvent falls back to DSN org id when explicit orgId is empty`() {
val options =
SentryOptions().apply {
dsn = "https://key@o123.ingest.sentry.io/456"
orgId = ""
}
val event = SentryEvent()
event.contexts.setTrace(SpanContext("test-op"))

val baggage = Baggage.fromEvent(event, "test-transaction", options)

assertEquals("123", baggage.orgId)
}

@Test
fun `baggage header does not include org id when both explicit and DSN org ids are empty`() {
val options =
SentryOptions().apply {
dsn = "https://key@sentry.io/456"
orgId = ""
release = "1.0.0"
}
val scope = Scope(options)
val baggage = Baggage(logger)

baggage.setValuesFromScope(scope, options)
val headerString = baggage.toHeaderString(null)

// Should not contain sentry-org_id if both explicit and DSN org ids are null/empty
assertFalse(headerString.contains("sentry-org_id"))
}

/**
* token = 1*tchar tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^" / "_" /
* "`" / "|" / "~" / DIGIT / ALPHA ; any VCHAR, except delimiters
Expand Down
40 changes: 40 additions & 0 deletions sentry/src/test/java/io/sentry/SentryOptionsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1032,4 +1032,44 @@ class SentryOptionsTest {
options.dsn = "https://key@sentry.io/456"
assertNull(options.effectiveOrgId)
}

@Test
fun `getEffectiveOrgId falls back to DSN when explicit orgId is empty string`() {
val options = SentryOptions()
options.dsn = "https://key@o123.ingest.sentry.io/456"
options.orgId = ""
assertEquals("123", options.effectiveOrgId)
}

@Test
fun `getEffectiveOrgId falls back to DSN when explicit orgId is whitespace only`() {
val options = SentryOptions()
options.dsn = "https://key@o123.ingest.sentry.io/456"
options.orgId = " "
assertEquals("123", options.effectiveOrgId)
}

@Test
fun `getEffectiveOrgId falls back to DSN when explicit orgId is tab and newline`() {
val options = SentryOptions()
options.dsn = "https://key@o123.ingest.sentry.io/456"
options.orgId = "\t\n"
assertEquals("123", options.effectiveOrgId)
}

@Test
fun `getEffectiveOrgId returns null when explicit orgId is empty and no DSN orgId`() {
val options = SentryOptions()
options.dsn = "https://key@sentry.io/456"
options.orgId = ""
assertNull(options.effectiveOrgId)
}

@Test
fun `getEffectiveOrgId trims whitespace from explicit orgId`() {
val options = SentryOptions()
options.dsn = "https://key@o123.ingest.sentry.io/456"
options.orgId = " 999 "
assertEquals("999", options.effectiveOrgId)
}
}
24 changes: 24 additions & 0 deletions sentry/src/test/java/io/sentry/util/TracingUtilsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -566,4 +566,28 @@ class TracingUtilsTest {
val options = makeOptions(dsnOrgId = null, strict = true)
assertTrue(TracingUtils.shouldContinueTrace(options, makeBaggage(null)))
}

@Test
fun `shouldContinueTrace uses DSN fallback when explicit orgId is empty`() {
val options = makeOptions(dsnOrgId = "123", explicitOrgId = "", strict = true)
assertTrue(TracingUtils.shouldContinueTrace(options, makeBaggage("123")))
}

@Test
fun `shouldContinueTrace uses DSN fallback when explicit orgId is whitespace`() {
val options = makeOptions(dsnOrgId = "123", explicitOrgId = " ", strict = true)
assertTrue(TracingUtils.shouldContinueTrace(options, makeBaggage("123")))
}

@Test
fun `shouldContinueTrace rejects mismatch after empty explicit orgId falls back to DSN`() {
val options = makeOptions(dsnOrgId = "123", explicitOrgId = "", strict = true)
assertFalse(TracingUtils.shouldContinueTrace(options, makeBaggage("999")))
}

@Test
fun `shouldContinueTrace strict=false with empty explicit orgId uses DSN fallback`() {
val options = makeOptions(dsnOrgId = "123", explicitOrgId = "", strict = false)
assertTrue(TracingUtils.shouldContinueTrace(options, makeBaggage("123")))
}
}