Skip to content

fix(tracing): Skip child span creation in streaming path when no current span (AI agents)#6813

Closed
sentrivana wants to merge 1 commit into
masterfrom
ivana/streaming-child-spans-ai-agents
Closed

fix(tracing): Skip child span creation in streaming path when no current span (AI agents)#6813
sentrivana wants to merge 1 commit into
masterfrom
ivana/streaming-child-spans-ai-agents

Conversation

@sentrivana

Copy link
Copy Markdown
Contributor

Summary

  • When span streaming is enabled and there is no current span, AI agent integrations should not create new root segments for child-span operations
  • Adds a sentry_sdk.traces.get_current_span() is None guard before creating spans in the streaming path
  • Affected integrations: openai_agents, pydantic_ai

Test plan

  • Existing tests pass
  • Verify that in streaming mode, no orphan root segments are created for AI agent operations when there's no active span

🤖 Generated with Claude Code

…ent span (AI agents)

When span streaming is enabled and there is no current span,
AI agent integrations should not create new root segments for
child-span operations. This adds a guard check using
`sentry_sdk.traces.get_current_span()` before creating spans
in the streaming path.

Affected integrations: openai_agents, pydantic_ai.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment on lines 34 to +44
if span_streaming:
span = sentry_sdk.traces.start_span(
name=f"chat {model_name}",
attributes={
"sentry.op": OP.GEN_AI_CHAT,
"sentry.origin": SPAN_ORIGIN,
SPANDATA.GEN_AI_OPERATION_NAME: "chat",
},
)
span = None
if sentry_sdk.traces.get_current_span() is not None:
span = sentry_sdk.traces.start_span(
name=f"chat {model_name}",
attributes={
"sentry.op": OP.GEN_AI_CHAT,
"sentry.origin": SPAN_ORIGIN,
SPANDATA.GEN_AI_OPERATION_NAME: "chat",
},
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ai_client_span can return None, breaking with ... as span in streaming path

In streaming mode with no current span, ai_client_span now returns None, but callers use it via with ai_client_span(...) as span, which raises AttributeError: __enter__ on None.

Evidence
  • ai_client_span sets span = None and only assigns a span when get_current_span() is not None, so it returns None in the guarded streaming case.
  • patches/models.py:115 (with ai_client_span(agent, kwargs) as span:) and models.py:154 (with ai_client_span(agent, span_kwargs) as span:) use the return value as a context manager.
  • None has no __enter__/__exit__, so entering the with block raises AttributeError before any span logic runs.
  • Downstream update_ai_client_span(span, ...) and span.set_attribute in the streaming wrapper also dereference span without a None guard.
Also found at 3 additional locations
  • sentry_sdk/integrations/openai_agents/spans/execute_tool.py:23-35
  • sentry_sdk/integrations/openai_agents/spans/invoke_agent.py:32-41
  • sentry_sdk/integrations/openai_agents/spans/agent_workflow.py:21-28

Identified by Warden code-review · P4J-HWE

Comment on lines 284 to +317
@@ -302,16 +304,17 @@ def ai_client_span(
# Set streaming flag from contextvar
span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, get_is_streaming())

_set_agent_data(span, agent)
_set_model_data(span, model, model_settings)
if span is not None:
_set_agent_data(span, agent)
_set_model_data(span, model, model_settings)

# Add available tools if agent is available
agent_obj = agent or get_current_agent()
_set_available_tools(span, agent_obj)
# Add available tools if agent is available
agent_obj = agent or get_current_agent()
_set_available_tools(span, agent_obj)

# Set input messages (full conversation history)
if messages:
_set_input_messages(span, messages)
# Set input messages (full conversation history)
if messages:
_set_input_messages(span, messages)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ai_client_span can return None but callers use it as a context manager

When streaming is enabled and there is no current span, ai_client_span now returns None, but callers do with ai_client_span(...) as span: (graph_nodes.py:62, 89) and span.__enter__() (init.py), which will raise a TypeError/AttributeError on None.

Evidence
  • New span = None branch is returned unchanged when get_current_span() is None.
  • graph_nodes.py wraps the result in with ai_client_span(...) as span: (lines 62 and 89); with None raises TypeError: 'NoneType' object does not support the context manager protocol.
  • pydantic_ai/__init__.py calls span.__enter__() directly after assigning the return value, which raises AttributeError on None.
  • The declared return type Union[Span, StreamedSpan] does not include None, so callers are not prompted to guard against it.
Also found at 2 additional locations
  • sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py:71
  • sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py:53-54

Identified by Warden code-review · MGK-CD6

Comment on lines +21 to 28
span = None
if sentry_sdk.traces.get_current_span() is not None:
span = sentry_sdk.traces.start_span(
name=f"{agent.name} workflow",
attributes={"sentry.origin": SPAN_ORIGIN},
)

return span

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

agent_workflow_span can return None, crashing both callers

In streaming mode with no current span this returns None, but callers do with agent_workflow_span(agent) as workflow_span: and workflow_span.__enter__(), both of which raise AttributeError on None.

Evidence
  • The new branch sets span = None and only assigns a real span when get_current_span() is not None, so return span can return None.
  • runner.py:45 uses with agent_workflow_span(agent) as workflow_span:; entering a with None block raises AttributeError: __enter__.
  • runner.py:156-157 does workflow_span = agent_workflow_span(agent) then workflow_span.__enter__(), dereferencing None with no guard.
  • Neither caller checks the return value for None before using it.
Also found at 6 additional locations
  • sentry_sdk/integrations/openai_agents/spans/ai_client.py:58
  • sentry_sdk/integrations/openai_agents/spans/invoke_agent.py:33-41
  • sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py:72
  • sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py:53-63
  • sentry_sdk/integrations/pydantic_ai/spans/ai_client.py:319
  • sentry_sdk/integrations/openai_agents/spans/execute_tool.py:22-22

Identified by Warden find-bugs · WP8-UZK

@sentrivana sentrivana closed this Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant