diff --git a/src/agents/tracing/spans.py b/src/agents/tracing/spans.py index 8f564beedb..a13ef605c6 100644 --- a/src/agents/tracing/spans.py +++ b/src/agents/tracing/spans.py @@ -222,11 +222,12 @@ class NoOpSpan(Span[TSpanData]): span_data: The operation-specific data for this span. """ - __slots__ = ("_span_data", "_prev_span_token") + __slots__ = ("_span_data", "_prev_span_token", "_started") def __init__(self, span_data: TSpanData): self._span_data = span_data self._prev_span_token: contextvars.Token[Span[TSpanData] | None] | None = None + self._started = False @property def trace_id(self) -> str: @@ -245,6 +246,10 @@ def parent_id(self) -> str | None: return None def start(self, mark_as_current: bool = False): + if self._started: + return + + self._started = True if mark_as_current: self._prev_span_token = Scope.set_current_span(self) diff --git a/src/agents/tracing/traces.py b/src/agents/tracing/traces.py index a703acf317..637c63d750 100644 --- a/src/agents/tracing/traces.py +++ b/src/agents/tracing/traces.py @@ -429,7 +429,6 @@ def __enter__(self) -> Trace: logger.error("Trace already started but no context token set") return self - self._started = True self.start(mark_as_current=True) return self @@ -441,6 +440,10 @@ def __exit__(self, exc_type, exc_val, exc_tb): self.finish(reset_current=True) def start(self, mark_as_current: bool = False): + if self._started: + return + + self._started = True if mark_as_current: self._prev_context_token = Scope.set_current_trace(self) diff --git a/tests/test_noop_tracing_start_idempotent.py b/tests/test_noop_tracing_start_idempotent.py new file mode 100644 index 0000000000..f29c36a044 --- /dev/null +++ b/tests/test_noop_tracing_start_idempotent.py @@ -0,0 +1,30 @@ +from __future__ import annotations + +from agents.tracing.scope import Scope +from agents.tracing.span_data import AgentSpanData +from agents.tracing.spans import NoOpSpan +from agents.tracing.traces import NoOpTrace + + +def test_noop_trace_repeated_start_does_not_overwrite_context_token() -> None: + trace = NoOpTrace() + try: + trace.start(mark_as_current=True) + trace.start(mark_as_current=True) + trace.finish(reset_current=True) + + assert Scope.get_current_trace() is None + finally: + Scope.set_current_trace(None) + + +def test_noop_span_repeated_start_does_not_overwrite_context_token() -> None: + span = NoOpSpan(AgentSpanData(name="test")) + try: + span.start(mark_as_current=True) + span.start(mark_as_current=True) + span.finish(reset_current=True) + + assert Scope.get_current_span() is None + finally: + Scope.set_current_span(None)