From 67ff769f6b91e18e2b84f29b9f8ff6fb063fe47c Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 20 May 2026 10:08:11 +0200 Subject: [PATCH 01/10] fix(openai-agents): Stop setting transaction status when child span fails --- .../openai_agents/patches/agent_run.py | 113 +++++++++++------- .../openai_agents/patches/runner.py | 28 ++++- .../openai_agents/spans/__init__.py | 2 +- .../openai_agents/spans/invoke_agent.py | 89 +++++++++----- .../integrations/openai_agents/utils.py | 3 - 5 files changed, 155 insertions(+), 80 deletions(-) diff --git a/sentry_sdk/integrations/openai_agents/patches/agent_run.py b/sentry_sdk/integrations/openai_agents/patches/agent_run.py index 01119589ad..65ff7fa80e 100644 --- a/sentry_sdk/integrations/openai_agents/patches/agent_run.py +++ b/sentry_sdk/integrations/openai_agents/patches/agent_run.py @@ -3,13 +3,13 @@ from sentry_sdk.consts import SPANDATA from sentry_sdk.integrations import DidNotEnable -from sentry_sdk.tracing_utils import set_span_errored from sentry_sdk.utils import capture_internal_exceptions, reraise from ..spans import ( - end_invoke_agent_span, + _AgentInvocationSpanContext, handoff_span, invoke_agent_span, + update_invoke_agent_span, ) if TYPE_CHECKING: @@ -65,7 +65,9 @@ def _maybe_start_agent_span( if _has_active_agent_span(context_wrapper): current_agent = _get_current_agent(context_wrapper) if current_agent and current_agent != agent: - end_invoke_agent_span(context_wrapper, current_agent) + _AgentInvocationSpanContext( + context_wrapper=context_wrapper, agent=current_agent + ).__exit__(None, None, None) # Store the agent on the context wrapper so we can access it later context_wrapper._sentry_current_agent = agent @@ -103,15 +105,23 @@ async def _run_single_turn( context_wrapper, agent, should_run_agent_start_hooks, kwargs ) + if span is None or span.timestamp is not None: + return await original_run_single_turn(*args, **kwargs) + try: - result = await original_run_single_turn(*args, **kwargs) + return await original_run_single_turn(*args, **kwargs) except Exception: - if span is not None and span.timestamp is None: - set_span_errored(span) - end_invoke_agent_span(context_wrapper, agent) - reraise(*sys.exc_info()) + exc_info = sys.exc_info() + with capture_internal_exceptions(): + span = getattr(context_wrapper, "_sentry_agent_span", None) + if not span: + return - return result + update_invoke_agent_span(span=span, context=context_wrapper, agent=agent) + + span.__exit__(*exc_info) + delattr(context_wrapper, "_sentry_agent_span") + reraise(*exc_info) async def _run_single_turn_streamed( @@ -174,18 +184,20 @@ async def _run_single_turn_streamed( is_streaming=True, ) - try: - result = await original_run_single_turn_streamed(*args, **kwargs) - except Exception: - exc_info = sys.exc_info() - with capture_internal_exceptions(): - if span is not None and span.timestamp is None: - set_span_errored(span) - end_invoke_agent_span(context_wrapper, agent) - _close_streaming_workflow_span(agent) - reraise(*exc_info) + if span is None or span.timestamp is not None: + return await original_run_single_turn_streamed(*args, **kwargs) - return result + with _AgentInvocationSpanContext( + context_wrapper=context_wrapper, + agent=agent, + ): + try: + return await original_run_single_turn_streamed(*args, **kwargs) + except Exception: + exc_info = sys.exc_info() + with capture_internal_exceptions(): + _close_streaming_workflow_span(agent) + reraise(*exc_info) async def _execute_handoffs( @@ -211,18 +223,30 @@ async def _execute_handoffs( handoff_agent_name = first_handoff.handoff.agent_name handoff_span(context_wrapper, agent, handoff_agent_name) - # Call original method with all parameters - try: - result = await original_execute_handoffs(*args, **kwargs) - except Exception: - exc_info = sys.exc_info() - with capture_internal_exceptions(): - _close_streaming_workflow_span(agent) - reraise(*exc_info) - finally: - # End span for current agent after handoff processing is complete - if agent and context_wrapper and _has_active_agent_span(context_wrapper): - end_invoke_agent_span(context_wrapper, agent) + if not agent or not context_wrapper or not _has_active_agent_span(context_wrapper): + # Call original method with all parameters + try: + result = await original_execute_handoffs(*args, **kwargs) + except Exception: + exc_info = sys.exc_info() + with capture_internal_exceptions(): + _close_streaming_workflow_span(agent) + reraise(*exc_info) + + return result + + with _AgentInvocationSpanContext( + context_wrapper=context_wrapper, + agent=agent, + ): + # Call original method with all parameters + try: + result = await original_execute_handoffs(*args, **kwargs) + except Exception: + exc_info = sys.exc_info() + with capture_internal_exceptions(): + _close_streaming_workflow_span(agent) + reraise(*exc_info) return result @@ -243,13 +267,22 @@ async def _execute_final_output( context_wrapper = kwargs.get("context_wrapper") final_output = kwargs.get("final_output") - try: - result = await original_execute_final_output(*args, **kwargs) - finally: - with capture_internal_exceptions(): - if agent and context_wrapper and _has_active_agent_span(context_wrapper): - end_invoke_agent_span(context_wrapper, agent, final_output) - # For streaming, close the workflow span (non-streaming uses context manager in _create_run_wrapper) - _close_streaming_workflow_span(agent) + if not agent or not context_wrapper or not _has_active_agent_span(context_wrapper): + try: + result = await original_execute_final_output(*args, **kwargs) + finally: + with capture_internal_exceptions(): + # For streaming, close the workflow span (non-streaming uses context manager in _create_run_wrapper) + _close_streaming_workflow_span(agent) + + with _AgentInvocationSpanContext( + context_wrapper=context_wrapper, agent=agent, output=final_output + ): + try: + result = await original_execute_final_output(*args, **kwargs) + finally: + with capture_internal_exceptions(): + # For streaming, close the workflow span (non-streaming uses context manager in _create_run_wrapper) + _close_streaming_workflow_span(agent) return result diff --git a/sentry_sdk/integrations/openai_agents/patches/runner.py b/sentry_sdk/integrations/openai_agents/patches/runner.py index 0674df7e70..6828ab4855 100644 --- a/sentry_sdk/integrations/openai_agents/patches/runner.py +++ b/sentry_sdk/integrations/openai_agents/patches/runner.py @@ -4,10 +4,9 @@ import sentry_sdk from sentry_sdk.consts import SPANDATA from sentry_sdk.integrations import DidNotEnable -from sentry_sdk.tracing_utils import set_span_errored from sentry_sdk.utils import capture_internal_exceptions, reraise -from ..spans import agent_workflow_span, end_invoke_agent_span +from ..spans import agent_workflow_span, update_invoke_agent_span from ..utils import _capture_exception try: @@ -66,8 +65,14 @@ async def wrapper(*args: "Any", **kwargs: "Any") -> "Any": invoke_agent_span is not None and invoke_agent_span.timestamp is None ): - set_span_errored(invoke_agent_span) - end_invoke_agent_span(context_wrapper, agent) + update_invoke_agent_span( + span=invoke_agent_span, + context=context_wrapper, + agent=agent, + ) + + invoke_agent_span.__exit__(*exc_info) + delattr(context_wrapper, "_sentry_agent_span") reraise(*exc_info) except Exception as exc: exc_info = sys.exc_info() @@ -78,7 +83,20 @@ async def wrapper(*args: "Any", **kwargs: "Any") -> "Any": _capture_exception(exc) reraise(*exc_info) - end_invoke_agent_span(run_result.context_wrapper, agent) + invoke_agent_span = getattr( + run_result.context_wrapper, "_sentry_agent_span", None + ) + if not invoke_agent_span: + return run_result + + update_invoke_agent_span( + span=invoke_agent_span, + context=run_result.context_wrapper, + agent=agent, + ) + + invoke_agent_span.__exit__(None, None, None) + delattr(run_result.context_wrapper, "_sentry_agent_span") return run_result return wrapper diff --git a/sentry_sdk/integrations/openai_agents/spans/__init__.py b/sentry_sdk/integrations/openai_agents/spans/__init__.py index a137b13040..67cc9be58e 100644 --- a/sentry_sdk/integrations/openai_agents/spans/__init__.py +++ b/sentry_sdk/integrations/openai_agents/spans/__init__.py @@ -3,7 +3,7 @@ from .execute_tool import execute_tool_span, update_execute_tool_span # noqa: F401 from .handoff import handoff_span # noqa: F401 from .invoke_agent import ( - end_invoke_agent_span, # noqa: F401 + _AgentInvocationSpanContext, # noqa: F401 invoke_agent_span, # noqa: F401 update_invoke_agent_span, # noqa: F401 ) diff --git a/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py b/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py index 99cbc0e0a4..d145f3e573 100644 --- a/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +++ b/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py @@ -9,7 +9,7 @@ ) from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.scope import should_send_default_pii -from sentry_sdk.utils import safe_serialize +from sentry_sdk.utils import capture_internal_exceptions, safe_serialize from ..consts import SPAN_ORIGIN from ..utils import _set_agent_data, _set_usage_data @@ -85,37 +85,64 @@ def invoke_agent_span( def update_invoke_agent_span( - context: "agents.RunContextWrapper", agent: "agents.Agent", output: "Any" + span: "sentry_sdk.tracing.Span", + context: "agents.RunContextWrapper", + agent: "agents.Agent", + output: "Any" = None, ) -> None: - span = getattr(context, "_sentry_agent_span", None) - - if span: - # Add aggregated usage data from context_wrapper - if hasattr(context, "usage"): - _set_usage_data(span, context.usage) + # Add aggregated usage data from context_wrapper + if hasattr(context, "usage"): + _set_usage_data(span, context.usage) - if should_send_default_pii(): - set_data_normalized( - span, SPANDATA.GEN_AI_RESPONSE_TEXT, output, unpack=False + if should_send_default_pii(): + set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, output, unpack=False) + + # Add conversation ID from agent + conv_id = getattr(agent, "_sentry_conversation_id", None) + if conv_id: + span.set_data(SPANDATA.GEN_AI_CONVERSATION_ID, conv_id) + + +class _AgentInvocationSpanContext: + """ + Sets accumulated data on the Invoke Agent span and finishes the span on exit. + Is a no-op if the context wrapper has no span set, i.e., when the span has already been finished. + """ + + def __init__( + self, + context_wrapper: "agents.RunContextWrapper", + agent: "agents.Agent", + output: "Optional[Any]" = None, + ) -> None: + self._context_wrapper = context_wrapper + self._agent = agent + self._output = output + + def __enter__(self) -> "_AgentInvocationSpanContext": + return self + + def __exit__( + self, + exc_type: "Optional[type[BaseException]]", + exc_val: "Optional[BaseException]", + exc_tb: "Optional[Any]", + ) -> None: + with capture_internal_exceptions(): + # Clear the stored agent + if hasattr(self._context_wrapper, "_sentry_current_agent"): + delattr(self._context_wrapper, "_sentry_current_agent") + + span = getattr(self._context_wrapper, "_sentry_agent_span", None) + if not span: + return + + update_invoke_agent_span( + span=span, + context=self._context_wrapper, + agent=self._agent, + output=self._output, ) - # Add conversation ID from agent - conv_id = getattr(agent, "_sentry_conversation_id", None) - if conv_id: - span.set_data(SPANDATA.GEN_AI_CONVERSATION_ID, conv_id) - - span.__exit__(None, None, None) - delattr(context, "_sentry_agent_span") - - -def end_invoke_agent_span( - context_wrapper: "agents.RunContextWrapper", - agent: "agents.Agent", - output: "Optional[Any]" = None, -) -> None: - """End the agent invocation span""" - # Clear the stored agent - if hasattr(context_wrapper, "_sentry_current_agent"): - delattr(context_wrapper, "_sentry_current_agent") - - update_invoke_agent_span(context_wrapper, agent, output) + span.__exit__(exc_type, exc_val, exc_tb) + delattr(self._context_wrapper, "_sentry_agent_span") diff --git a/sentry_sdk/integrations/openai_agents/utils.py b/sentry_sdk/integrations/openai_agents/utils.py index 1d7d9e629d..e4157418a3 100644 --- a/sentry_sdk/integrations/openai_agents/utils.py +++ b/sentry_sdk/integrations/openai_agents/utils.py @@ -17,7 +17,6 @@ from sentry_sdk.consts import OP, SPANDATA, SPANSTATUS from sentry_sdk.integrations import DidNotEnable from sentry_sdk.scope import should_send_default_pii -from sentry_sdk.tracing_utils import set_span_errored from sentry_sdk.utils import event_from_exception, safe_serialize if TYPE_CHECKING: @@ -35,8 +34,6 @@ def _capture_exception(exc: "Any") -> None: - set_span_errored() - event, hint = event_from_exception( exc, client_options=sentry_sdk.get_client().options, From 36eb8d7154918f087759aee07dc7e04088caf0b8 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 20 May 2026 10:11:00 +0200 Subject: [PATCH 02/10] . --- .../integrations/openai_agents/patches/agent_run.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sentry_sdk/integrations/openai_agents/patches/agent_run.py b/sentry_sdk/integrations/openai_agents/patches/agent_run.py index 65ff7fa80e..dcbc8a569f 100644 --- a/sentry_sdk/integrations/openai_agents/patches/agent_run.py +++ b/sentry_sdk/integrations/openai_agents/patches/agent_run.py @@ -65,9 +65,14 @@ def _maybe_start_agent_span( if _has_active_agent_span(context_wrapper): current_agent = _get_current_agent(context_wrapper) if current_agent and current_agent != agent: - _AgentInvocationSpanContext( - context_wrapper=context_wrapper, agent=current_agent - ).__exit__(None, None, None) + span = getattr(context_wrapper, "_sentry_agent_span", None) + if not span: + return + + update_invoke_agent_span(span=span, context=context_wrapper, agent=agent) + + span.__exit__(None, None, None) + delattr(context_wrapper, "_sentry_agent_span") # Store the agent on the context wrapper so we can access it later context_wrapper._sentry_current_agent = agent From 35bd5f7b7044f85bca2b48669839fa8b492d1198 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 20 May 2026 10:15:57 +0200 Subject: [PATCH 03/10] . --- .../integrations/openai_agents/patches/agent_run.py | 12 ++++++------ .../openai_agents/patches/error_tracing.py | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sentry_sdk/integrations/openai_agents/patches/agent_run.py b/sentry_sdk/integrations/openai_agents/patches/agent_run.py index dcbc8a569f..ce9079a243 100644 --- a/sentry_sdk/integrations/openai_agents/patches/agent_run.py +++ b/sentry_sdk/integrations/openai_agents/patches/agent_run.py @@ -66,13 +66,13 @@ def _maybe_start_agent_span( current_agent = _get_current_agent(context_wrapper) if current_agent and current_agent != agent: span = getattr(context_wrapper, "_sentry_agent_span", None) - if not span: - return + if span: + update_invoke_agent_span( + span=span, context=context_wrapper, agent=agent + ) - update_invoke_agent_span(span=span, context=context_wrapper, agent=agent) - - span.__exit__(None, None, None) - delattr(context_wrapper, "_sentry_agent_span") + span.__exit__(None, None, None) + delattr(context_wrapper, "_sentry_agent_span") # Store the agent on the context wrapper so we can access it later context_wrapper._sentry_current_agent = agent diff --git a/sentry_sdk/integrations/openai_agents/patches/error_tracing.py b/sentry_sdk/integrations/openai_agents/patches/error_tracing.py index e22d5fbd86..269380f501 100644 --- a/sentry_sdk/integrations/openai_agents/patches/error_tracing.py +++ b/sentry_sdk/integrations/openai_agents/patches/error_tracing.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING import sentry_sdk -from sentry_sdk.tracing_utils import set_span_errored +from sentry_sdk.consts import SPANSTATUS if TYPE_CHECKING: from typing import Any @@ -56,7 +56,7 @@ def sentry_attach_error_to_current_span( # Set the current Sentry span to errored current_span = sentry_sdk.get_current_span() if current_span is not None: - set_span_errored(current_span) + current_span.set_status(SPANSTATUS.INTERNAL_ERROR) # Call the original function return original_attach_error(error, *args, **kwargs) From 8fecde26e6ba547cd354fd2226bafccaa7b71b7d Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 20 May 2026 10:36:10 +0200 Subject: [PATCH 04/10] cleanup --- .../integrations/openai_agents/patches/agent_run.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/sentry_sdk/integrations/openai_agents/patches/agent_run.py b/sentry_sdk/integrations/openai_agents/patches/agent_run.py index ce9079a243..a4efe133d7 100644 --- a/sentry_sdk/integrations/openai_agents/patches/agent_run.py +++ b/sentry_sdk/integrations/openai_agents/patches/agent_run.py @@ -118,10 +118,6 @@ async def _run_single_turn( except Exception: exc_info = sys.exc_info() with capture_internal_exceptions(): - span = getattr(context_wrapper, "_sentry_agent_span", None) - if not span: - return - update_invoke_agent_span(span=span, context=context_wrapper, agent=agent) span.__exit__(*exc_info) @@ -231,15 +227,13 @@ async def _execute_handoffs( if not agent or not context_wrapper or not _has_active_agent_span(context_wrapper): # Call original method with all parameters try: - result = await original_execute_handoffs(*args, **kwargs) + return await original_execute_handoffs(*args, **kwargs) except Exception: exc_info = sys.exc_info() with capture_internal_exceptions(): _close_streaming_workflow_span(agent) reraise(*exc_info) - return result - with _AgentInvocationSpanContext( context_wrapper=context_wrapper, agent=agent, From 2dc0251beb1915d80975c699a26df722a85cb160 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 20 May 2026 10:38:50 +0200 Subject: [PATCH 05/10] . --- .../integrations/openai_agents/patches/agent_run.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/openai_agents/patches/agent_run.py b/sentry_sdk/integrations/openai_agents/patches/agent_run.py index a4efe133d7..7f582a0297 100644 --- a/sentry_sdk/integrations/openai_agents/patches/agent_run.py +++ b/sentry_sdk/integrations/openai_agents/patches/agent_run.py @@ -114,7 +114,7 @@ async def _run_single_turn( return await original_run_single_turn(*args, **kwargs) try: - return await original_run_single_turn(*args, **kwargs) + result = await original_run_single_turn(*args, **kwargs) except Exception: exc_info = sys.exc_info() with capture_internal_exceptions(): @@ -124,6 +124,8 @@ async def _run_single_turn( delattr(context_wrapper, "_sentry_agent_span") reraise(*exc_info) + return result + async def _run_single_turn_streamed( original_run_single_turn_streamed: "Callable[..., Awaitable[SingleStepResult]]", @@ -193,13 +195,15 @@ async def _run_single_turn_streamed( agent=agent, ): try: - return await original_run_single_turn_streamed(*args, **kwargs) + result = await original_run_single_turn_streamed(*args, **kwargs) except Exception: exc_info = sys.exc_info() with capture_internal_exceptions(): _close_streaming_workflow_span(agent) reraise(*exc_info) + return result + async def _execute_handoffs( original_execute_handoffs: "Callable[..., SingleStepResult]", From 9b3d1da9e8f2a125839f3556b4a3f35261f21d02 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 20 May 2026 10:48:46 +0200 Subject: [PATCH 06/10] return early in execute_final_output --- sentry_sdk/integrations/openai_agents/patches/agent_run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/openai_agents/patches/agent_run.py b/sentry_sdk/integrations/openai_agents/patches/agent_run.py index 7f582a0297..764b00ee12 100644 --- a/sentry_sdk/integrations/openai_agents/patches/agent_run.py +++ b/sentry_sdk/integrations/openai_agents/patches/agent_run.py @@ -272,7 +272,7 @@ async def _execute_final_output( if not agent or not context_wrapper or not _has_active_agent_span(context_wrapper): try: - result = await original_execute_final_output(*args, **kwargs) + return await original_execute_final_output(*args, **kwargs) finally: with capture_internal_exceptions(): # For streaming, close the workflow span (non-streaming uses context manager in _create_run_wrapper) From ef81df7a5fec8b3b6fe6d287aaf6d683c10bdc3b Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 20 May 2026 11:11:33 +0200 Subject: [PATCH 07/10] simplify --- .../openai_agents/patches/agent_run.py | 87 ++++++++++++------- .../openai_agents/spans/__init__.py | 1 - .../openai_agents/spans/invoke_agent.py | 49 +---------- 3 files changed, 56 insertions(+), 81 deletions(-) diff --git a/sentry_sdk/integrations/openai_agents/patches/agent_run.py b/sentry_sdk/integrations/openai_agents/patches/agent_run.py index 764b00ee12..66bbcb35c0 100644 --- a/sentry_sdk/integrations/openai_agents/patches/agent_run.py +++ b/sentry_sdk/integrations/openai_agents/patches/agent_run.py @@ -6,7 +6,6 @@ from sentry_sdk.utils import capture_internal_exceptions, reraise from ..spans import ( - _AgentInvocationSpanContext, handoff_span, invoke_agent_span, update_invoke_agent_span, @@ -190,17 +189,16 @@ async def _run_single_turn_streamed( if span is None or span.timestamp is not None: return await original_run_single_turn_streamed(*args, **kwargs) - with _AgentInvocationSpanContext( - context_wrapper=context_wrapper, - agent=agent, - ): - try: - result = await original_run_single_turn_streamed(*args, **kwargs) - except Exception: - exc_info = sys.exc_info() - with capture_internal_exceptions(): - _close_streaming_workflow_span(agent) - reraise(*exc_info) + try: + result = await original_run_single_turn_streamed(*args, **kwargs) + except Exception: + exc_info = sys.exc_info() + with capture_internal_exceptions(): + update_invoke_agent_span(span=span, context=context_wrapper, agent=agent) + span.__exit__(*exc_info) + delattr(context_wrapper, "_sentry_agent_span") + _close_streaming_workflow_span(agent) + reraise(*exc_info) return result @@ -238,18 +236,27 @@ async def _execute_handoffs( _close_streaming_workflow_span(agent) reraise(*exc_info) - with _AgentInvocationSpanContext( - context_wrapper=context_wrapper, - agent=agent, - ): - # Call original method with all parameters - try: - result = await original_execute_handoffs(*args, **kwargs) - except Exception: - exc_info = sys.exc_info() - with capture_internal_exceptions(): - _close_streaming_workflow_span(agent) - reraise(*exc_info) + # Call original method with all parameters + try: + result = await original_execute_handoffs(*args, **kwargs) + except Exception: + exc_info = sys.exc_info() + with capture_internal_exceptions(): + _close_streaming_workflow_span(agent) + span = getattr(context_wrapper, "_sentry_agent_span", None) + if span: + update_invoke_agent_span( + span=span, context=context_wrapper, agent=agent + ) + span.__exit__(*exc_info) + delattr(context_wrapper, "_sentry_agent_span") + reraise(*exc_info) + + span = getattr(context_wrapper, "_sentry_agent_span", None) + if span: + update_invoke_agent_span(span=span, context=context_wrapper, agent=agent) + span.__exit__(None, None, None) + delattr(context_wrapper, "_sentry_agent_span") return result @@ -278,14 +285,28 @@ async def _execute_final_output( # For streaming, close the workflow span (non-streaming uses context manager in _create_run_wrapper) _close_streaming_workflow_span(agent) - with _AgentInvocationSpanContext( - context_wrapper=context_wrapper, agent=agent, output=final_output - ): - try: - result = await original_execute_final_output(*args, **kwargs) - finally: - with capture_internal_exceptions(): - # For streaming, close the workflow span (non-streaming uses context manager in _create_run_wrapper) - _close_streaming_workflow_span(agent) + try: + result = await original_execute_final_output(*args, **kwargs) + except Exception: + exc_info = sys.exc_info() + with capture_internal_exceptions(): + # For streaming, close the workflow span (non-streaming uses context manager in _create_run_wrapper) + _close_streaming_workflow_span(agent) + span = getattr(context_wrapper, "_sentry_agent_span", None) + if span: + update_invoke_agent_span( + span=span, context=context_wrapper, agent=agent, output=final_output + ) + span.__exit__(*exc_info) + delattr(context_wrapper, "_sentry_agent_span") + reraise(*exc_info) + + span = getattr(context_wrapper, "_sentry_agent_span", None) + if span: + update_invoke_agent_span( + span=span, context=context_wrapper, agent=agent, output=final_output + ) + span.__exit__(None, None, None) + delattr(context_wrapper, "_sentry_agent_span") return result diff --git a/sentry_sdk/integrations/openai_agents/spans/__init__.py b/sentry_sdk/integrations/openai_agents/spans/__init__.py index 67cc9be58e..08802a87a4 100644 --- a/sentry_sdk/integrations/openai_agents/spans/__init__.py +++ b/sentry_sdk/integrations/openai_agents/spans/__init__.py @@ -3,7 +3,6 @@ from .execute_tool import execute_tool_span, update_execute_tool_span # noqa: F401 from .handoff import handoff_span # noqa: F401 from .invoke_agent import ( - _AgentInvocationSpanContext, # noqa: F401 invoke_agent_span, # noqa: F401 update_invoke_agent_span, # noqa: F401 ) diff --git a/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py b/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py index d145f3e573..6f7dda3982 100644 --- a/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +++ b/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py @@ -9,13 +9,13 @@ ) from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.scope import should_send_default_pii -from sentry_sdk.utils import capture_internal_exceptions, safe_serialize +from sentry_sdk.utils import safe_serialize from ..consts import SPAN_ORIGIN from ..utils import _set_agent_data, _set_usage_data if TYPE_CHECKING: - from typing import Any, Optional + from typing import Any import agents @@ -101,48 +101,3 @@ def update_invoke_agent_span( conv_id = getattr(agent, "_sentry_conversation_id", None) if conv_id: span.set_data(SPANDATA.GEN_AI_CONVERSATION_ID, conv_id) - - -class _AgentInvocationSpanContext: - """ - Sets accumulated data on the Invoke Agent span and finishes the span on exit. - Is a no-op if the context wrapper has no span set, i.e., when the span has already been finished. - """ - - def __init__( - self, - context_wrapper: "agents.RunContextWrapper", - agent: "agents.Agent", - output: "Optional[Any]" = None, - ) -> None: - self._context_wrapper = context_wrapper - self._agent = agent - self._output = output - - def __enter__(self) -> "_AgentInvocationSpanContext": - return self - - def __exit__( - self, - exc_type: "Optional[type[BaseException]]", - exc_val: "Optional[BaseException]", - exc_tb: "Optional[Any]", - ) -> None: - with capture_internal_exceptions(): - # Clear the stored agent - if hasattr(self._context_wrapper, "_sentry_current_agent"): - delattr(self._context_wrapper, "_sentry_current_agent") - - span = getattr(self._context_wrapper, "_sentry_agent_span", None) - if not span: - return - - update_invoke_agent_span( - span=span, - context=self._context_wrapper, - agent=self._agent, - output=self._output, - ) - - span.__exit__(exc_type, exc_val, exc_tb) - delattr(self._context_wrapper, "_sentry_agent_span") From b085cf9632cba2953047fd0c5306333187b44a8e Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 20 May 2026 11:25:01 +0200 Subject: [PATCH 08/10] remove set_span_errored() --- sentry_sdk/tracing_utils.py | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/sentry_sdk/tracing_utils.py b/sentry_sdk/tracing_utils.py index 77e0a66a8f..0eb53871ef 100644 --- a/sentry_sdk/tracing_utils.py +++ b/sentry_sdk/tracing_utils.py @@ -20,7 +20,7 @@ from typing import TYPE_CHECKING import sentry_sdk -from sentry_sdk.consts import OP, SPANDATA, SPANSTATUS, SPANTEMPLATE +from sentry_sdk.consts import OP, SPANDATA, SPANTEMPLATE from sentry_sdk.utils import ( _is_external_source, _is_in_project_root, @@ -1201,33 +1201,6 @@ def get_current_span( return current_span -def set_span_errored(span: "Optional[Union[Span, StreamedSpan]]" = None) -> None: - """ - Set the status of the current or given span to INTERNAL_ERROR. - Also sets the status of the transaction (root span) to INTERNAL_ERROR. - """ - from sentry_sdk.traces import SpanStatus, StreamedSpan, _get_current_streamed_span - - client = sentry_sdk.get_client() - - if not span: - span = ( - _get_current_streamed_span() - if has_span_streaming_enabled(client.options) - else sentry_sdk.get_current_span() - ) - - if span is not None: - if isinstance(span, Span): - span.set_status(SPANSTATUS.INTERNAL_ERROR) - if span.containing_transaction is not None: - span.containing_transaction.set_status(SPANSTATUS.INTERNAL_ERROR) - elif isinstance(span, StreamedSpan): - span.status = SpanStatus.ERROR - if span._segment is not None: - span._segment.status = SpanStatus.ERROR - - def _generate_sample_rand( trace_id: "Optional[str]", *, From fab2069523f64b7b8a59443150b4373978e96edc Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 20 May 2026 11:33:12 +0200 Subject: [PATCH 09/10] whitespace --- sentry_sdk/integrations/openai_agents/patches/agent_run.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/sentry_sdk/integrations/openai_agents/patches/agent_run.py b/sentry_sdk/integrations/openai_agents/patches/agent_run.py index 66bbcb35c0..81e58dc8a7 100644 --- a/sentry_sdk/integrations/openai_agents/patches/agent_run.py +++ b/sentry_sdk/integrations/openai_agents/patches/agent_run.py @@ -69,7 +69,6 @@ def _maybe_start_agent_span( update_invoke_agent_span( span=span, context=context_wrapper, agent=agent ) - span.__exit__(None, None, None) delattr(context_wrapper, "_sentry_agent_span") @@ -118,7 +117,6 @@ async def _run_single_turn( exc_info = sys.exc_info() with capture_internal_exceptions(): update_invoke_agent_span(span=span, context=context_wrapper, agent=agent) - span.__exit__(*exc_info) delattr(context_wrapper, "_sentry_agent_span") reraise(*exc_info) From 4a7fcdbc1e9244de07ff0134e8355f42e0b2e84e Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 20 May 2026 11:43:05 +0200 Subject: [PATCH 10/10] ensure context_wrapper._sentry_agent_span exists before delattr --- .../openai_agents/patches/agent_run.py | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/sentry_sdk/integrations/openai_agents/patches/agent_run.py b/sentry_sdk/integrations/openai_agents/patches/agent_run.py index 81e58dc8a7..6e7f0f2820 100644 --- a/sentry_sdk/integrations/openai_agents/patches/agent_run.py +++ b/sentry_sdk/integrations/openai_agents/patches/agent_run.py @@ -116,9 +116,13 @@ async def _run_single_turn( except Exception: exc_info = sys.exc_info() with capture_internal_exceptions(): - update_invoke_agent_span(span=span, context=context_wrapper, agent=agent) - span.__exit__(*exc_info) - delattr(context_wrapper, "_sentry_agent_span") + span = getattr(context_wrapper, "_sentry_agent_span", None) + if span: + update_invoke_agent_span( + span=span, context=context_wrapper, agent=agent + ) + span.__exit__(*exc_info) + delattr(context_wrapper, "_sentry_agent_span") reraise(*exc_info) return result @@ -192,9 +196,13 @@ async def _run_single_turn_streamed( except Exception: exc_info = sys.exc_info() with capture_internal_exceptions(): - update_invoke_agent_span(span=span, context=context_wrapper, agent=agent) - span.__exit__(*exc_info) - delattr(context_wrapper, "_sentry_agent_span") + span = getattr(context_wrapper, "_sentry_agent_span", None) + if span: + update_invoke_agent_span( + span=span, context=context_wrapper, agent=agent + ) + span.__exit__(*exc_info) + delattr(context_wrapper, "_sentry_agent_span") _close_streaming_workflow_span(agent) reraise(*exc_info)