Skip to content

Mutable span and transaction options can affect already-created tracing objects #6416

Open

Description

@adinauer

Summary

SpanOptions and TransactionOptions are mutable configuration objects, but the SDK retains the caller-owned instances after creating a span or transaction. Consequently, modifying or reusing an options instance can change the behavior of an already-running span or transaction.

The SDK also mutates caller-provided SpanOptions in the timestamp-bearing startChild overloads. This can leak a timestamp into later calls that reuse the same options object.

This was surfaced while removing Instrumenter in #6252, where ActivityLifecycleIntegration reuses one SpanOptions instance for app-start, TTID, and TTFD spans.

Reproduction

Caller-visible mutation and stale timestamps

SpanOptions options = new SpanOptions();

parent.startChild("first", "description", explicitTimestamp, Instrumenter.SENTRY, options);

// The SDK has modified caller-owned state.
assert options.getStartTimestamp() == explicitTimestamp;

// This may unexpectedly reuse explicitTimestamp instead of starting now.
parent.startChild("second", "description", options);

Existing span behavior can change

SpanOptions options = new SpanOptions();
ISpan span = parent.startChild("operation", "description", options);

// Span retains the same options instance and reads trimming flags when finishing.
options.setTrimEnd(true);
span.finish();

The same applies to TransactionOptions: SentryTracer retains the supplied instance and continues reading values such as idleTimeout, deadlineTimeout, waitForChildren, and transactionFinishedCallback throughout the transaction lifetime.

Expected behavior

Options should be treated as point-in-time creation configuration:

  • The SDK should not mutate caller-provided options.
  • Changes to an options object after startChild or startTransaction should not affect the created span or transaction.
  • Reusing an options instance should not leak state between spans or transactions.
  • Native and OpenTelemetry implementations should have consistent behavior.

Actual behavior

  • Span retains its supplied SpanOptions and reads some fields later.
  • SentryTracer retains its supplied TransactionOptions and reads some fields later.
  • Timestamp-bearing startChild implementations mutate the supplied SpanOptions.
  • Reusing or concurrently sharing an options instance can produce stale or inconsistent behavior.

Relevant locations

  • sentry/src/main/java/io/sentry/Span.java
  • sentry/src/main/java/io/sentry/SentryTracer.java
  • sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSpanWrapper.java
  • sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java

Acceptance criteria

  • Span and transaction creation snapshot the relevant options rather than retaining caller-owned mutable state.
  • No startChild implementation modifies a supplied SpanOptions instance.
  • Tests cover options reuse, mutation after creation, and native/OpenTelemetry consistency.

Activity

  1. linear-code commented on Oct 29, 2026

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions