Skip to content

fix(otel): only set Scope.Transaction in SentrySpanProcessor when null#5337

Open
tsushanth wants to merge 2 commits into
getsentry:mainfrom
tsushanth:fix/span-processor-overwrite-transaction
Open

fix(otel): only set Scope.Transaction in SentrySpanProcessor when null#5337
tsushanth wants to merge 2 commits into
getsentry:mainfrom
tsushanth:fix/span-processor-overwrite-transaction

Conversation

@tsushanth

Copy link
Copy Markdown

Fixes #5314.

Problem

SentrySpanProcessor.OnStart calls ConfigureScope unconditionally:

_hub.ConfigureScope(static (scope, transaction) => scope.Transaction = transaction, transaction);

When two root OTel activities overlap and neither is a child of the other, this silently overwrites whatever transaction was already on the scope with the new one. As the issue notes, the intuitive default is to not overwrite an existing transaction.

Fix

Add a null-check guard inside the ConfigureScope lambda:

_hub.ConfigureScope(static (scope, transaction) =>
{
    if (scope.Transaction is null)
    {
        scope.Transaction = transaction;
    }
}, transaction);

Scope.Transaction is only set when it is currently null; an already-present transaction is left unchanged.

Test

Added OnStart_WithExistingTransactionOnScope_DoesNotOverwriteExistingTransaction to SentrySpanProcessorTests: sets a transaction on the scope, starts a new root activity, calls OnStart, and asserts the scope still holds the original transaction.

This is intentionally not bundled with #2399 (the AutoSetScopeTransactions opt-in), per the issue's notes.

@github-actions github-actions Bot added the risk: low PR risk score: low label Jul 3, 2026
Comment thread test/Sentry.OpenTelemetry.Tests/SentrySpanProcessorTests.cs
Comment thread src/Sentry.OpenTelemetry/SentrySpanProcessor.cs
sut.OnStart(data!);

// Assert - the pre-existing transaction must still be on the scope
ITransaction? scopeTransaction = null;

@Flash0ver Flash0ver Jul 3, 2026

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.

issue: test was neither run nor built

  • there is no ITransaction type
  • this project does not have Nullable Reference Types enabled

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed — rewrote the test to use the real Hub instead of a mock IInternalScopeManager. The mock was eating ConfigureScope calls so the guard never ran. The test now captures the first transaction from sut._map and verifies scope.Transaction still points to it after the second OnStart.

Comment on lines +1016 to +1017
[Fact]
public void OnStart_WithExistingTransactionOnScope_DoesNotOverwriteExistingTransaction()

@Flash0ver Flash0ver Jul 3, 2026

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.

issue: test is ineffective

Currently, this test does not actually cover the modification, and does not cover the modified method SentrySpanProcessor.CreateRootSpan.

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.

@jamescrosswell I wonder if Mutation Testing could cover such issues early ... e.g. if we only run mutation tests on the changes per PR, and only via the test cases that cover the /src/ changes of the PR.

@jamescrosswell jamescrosswell Jul 5, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

That might be nice. It has to save us more effort than we spend maintaining CI to automate it. In the case of mutation tests that seems likely. Let's add an issue for prioritisation.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed in the same rewrite — the test now goes through the real Hub's ConfigureScope, so the null guard in CreateRootSpan actually runs.

Comment on lines +1019 to +1021
// Arrange - simulate a root OTel activity starting while another transaction is
// already on the scope (e.g. two overlapping root activities). The processor should
// not overwrite the existing transaction (see GH-5314).

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.

thought: comments don't add additional value

IMO, the comments in this test apart from Arrange/Assert/Assert don't really add any additional value:

  • Assert: the existing transaction is called "Existing"
  • Act: the New Root Activity that is started is called "NewRootActivity"
    • also: actually, Act is missing for the Act portion of the test
  • Assert: the because string parameter to the assertion describes

To me, the combination of Test-Name + Test-Logic + String-Parameters already describe what this test is covering and asserting.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Plus the test itself is called OnStart_WithExistingTransactionOnScope_DoesNotOverwriteExistingTransaction...

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Removed the inline comments from the test.

Comment on lines +175 to +178
// Only set Scope.Transaction when it is currently null. Overwriting an existing
// transaction (which can happen when two root OTel activities overlap and neither is
// a child of the other) silently drops the previous transaction's scope context.
// See https://github.com/getsentry/sentry-dotnet/issues/5314

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.

suggestion: shorten comment

Although this comment does add value ... I find it a bit lengthy.
Perhaps we can shorten it a bit: for example: Only set Scope.Transaction when it is currently null is just describing the C# syntax in use.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think we could probably remove the comment entirely in this case... it's pretty apparent from the code what's going on.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Removed.

Comment on lines +175 to +178
// Only set Scope.Transaction when it is currently null. Overwriting an existing
// transaction (which can happen when two root OTel activities overlap and neither is
// a child of the other) silently drops the previous transaction's scope context.
// See https://github.com/getsentry/sentry-dotnet/issues/5314

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.

question: do we want to add links to issues more aggressively? @jamescrosswell

Previously, we only added links to the respective GitHub issue, when the code isn't too obvious what it's trying to achieve in a rather complex flow ... or when the solution in place should be reviewed/revisited at some point in time when it is a workaround.

But it seems that Coding Agents like to add both a summary from the GitHub issue, as well as a link to the GitHub issue, and additionally describe the code semantics ahead.

In this case, I would prefer to either have a one-liner, or two/three words + link to the GitHub issue ... but both - to me - is a bit lengthy. To be fair, I might exaggerate "the problem" here a bit. What do you think?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We get this for free from git blame (will identify the specific commit - relatively trivial to find the PR from there with a squash and merge as we're doing). When the code is particularly unintuitive and a full explanation in code comments isn't practical, it's worth linking to a PR description that provides a full explanation (make it clear we expect people will have to read the PR description to fully 'get it' in those cases).

For this piece of code, I don't think it's worth it though. We just don't want to overwrite previous/manually set values on the scope, which is obvious from the code.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Agreed, removed.

@Flash0ver

Copy link
Copy Markdown
Member

Thank you very much, @tsushanth, for your contribution to the Sentry .NET SDK!
I left some comments about the test-case, and commented on the comments 😉

Comment on lines +181 to +184
if (scope.Transaction is null)
{
scope.Transaction = transaction;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if (scope.Transaction is null)
{
scope.Transaction = transaction;
}
scope.Transaction ??= transaction;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Applied.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @tsushanth - did you forget to push your changes to remote maybe? I don't see them here...

Rewrite test to exercise CreateRootSpan directly via ScopeManager,
use correct types, and remove noisy inline comments per review feedback.
@tsushanth tsushanth force-pushed the fix/span-processor-overwrite-transaction branch from 8a3f87c to 8a20eba Compare July 6, 2026 14:30
@tsushanth

Copy link
Copy Markdown
Author

Thanks for the detailed review @Flash0ver! Updated:

  • Source comment: shortened to a two-liner with issue link
  • Test: rewritten to use ScopeManager/Scope directly (same pattern as OnStart_SampledWithoutParentSpanId_StartsNewTransaction), so it actually exercises CreateRootSpan. First activity sets scope.Transaction via the processor; second root activity is the act; assertion checks scope.Transaction is still the first
  • Types: removed ITransaction (doesn't exist here); scope.Transaction is tested via BeSameAs on the captured reference
  • Inline comments: removed the narrative comments, kept only Arrange/Act/Assert markers

@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 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.

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 8a20eba. Configure here.

Comment thread test/Sentry.OpenTelemetry.Tests/SentrySpanProcessorTests.cs
@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 74.20%. Comparing base (04a9854) to head (c9f0b84).
⚠️ Report is 7 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5337      +/-   ##
==========================================
+ Coverage   74.16%   74.20%   +0.04%     
==========================================
  Files         508      509       +1     
  Lines       18353    18386      +33     
  Branches     3586     3600      +14     
==========================================
+ Hits        13612    13644      +32     
  Misses       3869     3869              
- Partials      872      873       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tsushanth

Copy link
Copy Markdown
Author

Sorry about the delay — was pushing to the wrong local branch name. Commits are now on the correct branch: ??= operator in the source, and the test rewritten to use the real Hub (not a mock IInternalScopeManager) so ConfigureScope actually executes the null guard.

@Flash0ver

Copy link
Copy Markdown
Member

thanks ... I'll take another look in the morning (CEST)

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

Labels

risk: low PR risk score: low

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OpenTelemetry SpanProcessor should only set Scope.Transaction when it is null

3 participants