From 8c8db4bde42fb942e289d423980339a651374691 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 10 Jul 2026 15:07:21 +0200 Subject: [PATCH 1/2] fix(core): Set replayId in DSC for buffer mode error events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In buffer mode, the scope's replayId is empty until captureReplay() is called. The transaction's baggage is frozen before that, so the DSC in the envelope header has no replay_id — breaking error-to-replay linkage. After captureReplay() sets the replayId on scope, force-set it on the transaction's frozen baggage via Baggage.forceSetReplayId() so the envelope header carries the correct replay_id in the DSC. This matches the JS SDK's approach of updating the DSC after buffer-to- session conversion. Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 1 + sentry/api/sentry.api | 1 + sentry/src/main/java/io/sentry/Baggage.java | 7 +++++++ sentry/src/main/java/io/sentry/SentryClient.java | 12 ++++++++++++ 4 files changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6528ac7c02b..44bd1af87fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ ### Fixes - Session Replay: Fix first recording segment missing for replays in `buffer` mode ([#5753](https://github.com/getsentry/sentry-java/pull/5753)) +- Fix error-to-replay linkage in buffer mode ([#5754](https://github.com/getsentry/sentry-java/pull/5754)) - Fix main thread identification for tombstone (native crash) events ([#5742](https://github.com/getsentry/sentry-java/pull/5742)) ### Dependencies diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index c0b6fec780c..00183bc9b30 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -37,6 +37,7 @@ public final class io/sentry/Baggage { public fun (Lio/sentry/Baggage;)V public fun (Lio/sentry/ILogger;)V public fun (Ljava/util/concurrent/ConcurrentHashMap;Ljava/lang/Double;Ljava/lang/Double;Ljava/lang/String;ZZLio/sentry/ILogger;)V + public fun forceSetReplayId (Lio/sentry/protocol/SentryId;)V public fun forceSetSampleRate (Ljava/lang/Double;)V public fun freeze ()V public static fun fromEvent (Lio/sentry/SentryBaseEvent;Ljava/lang/String;Lio/sentry/SentryOptions;)Lio/sentry/Baggage; diff --git a/sentry/src/main/java/io/sentry/Baggage.java b/sentry/src/main/java/io/sentry/Baggage.java index 4645df3f3a4..e2114a915bc 100644 --- a/sentry/src/main/java/io/sentry/Baggage.java +++ b/sentry/src/main/java/io/sentry/Baggage.java @@ -451,6 +451,13 @@ public void setReplayId(final @Nullable String replayId) { set(DSCKeys.REPLAY_ID, replayId); } + @ApiStatus.Internal + public void forceSetReplayId(final @NotNull SentryId replayId) { + if (!SentryId.EMPTY_ID.equals(replayId)) { + keyValues.put(DSCKeys.REPLAY_ID, replayId.toString()); + } + } + @ApiStatus.Internal public @Nullable String getOrgId() { return get(DSCKeys.ORG_ID); diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index 02934b1ed7d..b20734b29d3 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -250,6 +250,18 @@ private boolean shouldApplyScopeData(final @NotNull CheckIn event, final @NotNul } if (shouldCaptureReplay) { options.getReplayController().captureReplay(event.isCrashed()); + if (scope != null) { + final @Nullable SentryId replayId = scope.getReplayId(); + if (replayId != null && !replayId.equals(SentryId.EMPTY_ID)) { + final @Nullable ITransaction transaction = scope.getTransaction(); + if (transaction != null) { + final @Nullable Baggage baggage = transaction.getSpanContext().getBaggage(); + if (baggage != null) { + baggage.forceSetReplayId(replayId); + } + } + } + } } } From 6f8932837b840114b0f11d085274cf7848f084af Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 10 Jul 2026 15:34:35 +0200 Subject: [PATCH 2/2] Add comment explaining forceSetReplayId Co-Authored-By: Claude Opus 4.6 (1M context) --- sentry/src/main/java/io/sentry/Baggage.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sentry/src/main/java/io/sentry/Baggage.java b/sentry/src/main/java/io/sentry/Baggage.java index e2114a915bc..9f0753f8671 100644 --- a/sentry/src/main/java/io/sentry/Baggage.java +++ b/sentry/src/main/java/io/sentry/Baggage.java @@ -451,6 +451,12 @@ public void setReplayId(final @Nullable String replayId) { set(DSCKeys.REPLAY_ID, replayId); } + /** + * Sets replay_id on the baggage bypassing the freeze check. In buffer mode the replay_id is + * unknown when the baggage is frozen, so it must be injected after {@link + * ReplayController#captureReplay} sets it on scope. This mirrors the JS SDK's setReplayIdOnDynamicSamplingContext. + */ @ApiStatus.Internal public void forceSetReplayId(final @NotNull SentryId replayId) { if (!SentryId.EMPTY_ID.equals(replayId)) {