diff --git a/src/agents/tracing/traces.py b/src/agents/tracing/traces.py index a703acf317..eda6d2f880 100644 --- a/src/agents/tracing/traces.py +++ b/src/agents/tracing/traces.py @@ -497,6 +497,7 @@ class TraceImpl(Trace): "_prev_context_token", "_processor", "_started", + "_finished", ) def __init__( @@ -516,6 +517,7 @@ def __init__( self._prev_context_token: contextvars.Token[Trace | None] | None = None self._processor = processor self._started = False + self._finished = False @property def trace_id(self) -> str: @@ -544,7 +546,9 @@ def finish(self, reset_current: bool = False): if not self._started: return - self._processor.on_trace_end(self) + if not self._finished: + self._processor.on_trace_end(self) + self._finished = True if reset_current and self._prev_context_token is not None: Scope.reset_current_trace(self._prev_context_token) diff --git a/tests/test_trace_finish_idempotent.py b/tests/test_trace_finish_idempotent.py new file mode 100644 index 0000000000..6f5dc47f90 --- /dev/null +++ b/tests/test_trace_finish_idempotent.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from unittest.mock import MagicMock + +from agents.tracing.scope import Scope +from agents.tracing.traces import TraceImpl + + +def test_trace_finish_notifies_processor_once_and_can_reset_later() -> None: + processor = MagicMock() + trace = TraceImpl( + name="workflow", + trace_id="trace_test", + group_id=None, + metadata=None, + processor=processor, + ) + + trace.start(mark_as_current=True) + trace.finish() + trace.finish(reset_current=True) + + processor.on_trace_end.assert_called_once_with(trace) + assert Scope.get_current_trace() is None