From 4c62a25fc9d2a66f7b5d3322d732f4797bf629f7 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 24 Sep 2026 11:30:58 +1200 Subject: [PATCH] fix: transaction processors receive the previous processor's result Each iteration passed the original transaction instead of the running result, so with more than one processor every intermediate result was discarded and only the last processor's output survived. Event processors already chain correctly. Co-Authored-By: Claude Opus 5 --- src/Sentry/SentryClient.cs | 2 +- test/Sentry.Tests/SentryClientTests.cs | 36 ++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/Sentry/SentryClient.cs b/src/Sentry/SentryClient.cs index c3c88a062e..d694425feb 100644 --- a/src/Sentry/SentryClient.cs +++ b/src/Sentry/SentryClient.cs @@ -212,7 +212,7 @@ public void CaptureTransaction(SentryTransaction transaction, Scope? scope, Sent var processedTransaction = transaction; foreach (var processor in scope.GetAllTransactionProcessors()) { - processedTransaction = processor.DoProcessTransaction(transaction, hint); + processedTransaction = processor.DoProcessTransaction(processedTransaction, hint); if (processedTransaction == null) // Rejected transaction { _options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.EventProcessor, DataCategory.Transaction); diff --git a/test/Sentry.Tests/SentryClientTests.cs b/test/Sentry.Tests/SentryClientTests.cs index 8329dad9a1..91af7baeeb 100644 --- a/test/Sentry.Tests/SentryClientTests.cs +++ b/test/Sentry.Tests/SentryClientTests.cs @@ -1638,6 +1638,42 @@ public void CaptureTransaction_AddedTransactionProcessor_ReceivesHint() processor.Received(1).Process(Arg.Any(), Arg.Any()); } + [Fact] + public void CaptureTransaction_MultipleTransactionProcessors_EachReceivesThePreviousResult() + { + // Arrange + var first = Substitute.For(); + var second = Substitute.For(); + var replacement = new SentryTransaction("replaced by first", "operation") + { + IsSampled = true, + EndTimestamp = DateTimeOffset.Now + }; + first.Process(Arg.Any()).Returns(replacement); + second.Process(Arg.Any()).Returns(callInfo => callInfo.Arg()); + _fixture.SentryOptions.AddTransactionProcessor(first); + _fixture.SentryOptions.AddTransactionProcessor(second); + + var transaction = new SentryTransaction("original", "operation") + { + IsSampled = true, + EndTimestamp = DateTimeOffset.Now + }; + + Envelope envelope = null; + _fixture.BackgroundWorker.EnqueueEnvelope(Arg.Do(arg => envelope = arg)).Returns(true); + + // Act + _fixture.GetSut().CaptureTransaction(transaction); + + // Assert + second.Received(1).Process(replacement); + envelope.Should().NotBeNull(); + var sent = envelope.Items.Select(i => i.Payload).OfType() + .Select(p => p.Source).OfType().Single(); + sent.Name.Should().Be("replaced by first"); + } + [Fact] public void CaptureTransaction_TransactionProcessor_ReceivesScopeAttachments() {