[do not merge] feat: Span streaming & new span API #5551
5 issues
find-bugs: Found 5 issues (3 high, 1 medium, 1 low)
High
get_start_span_function returns incompatible function signature when streaming is enabled - `sentry_sdk/ai/utils.py:537-540`
When span streaming is enabled or the current span is a StreamedSpan, get_start_span_function() returns sentry_sdk.traces.start_span which only accepts (name, attributes, parent_span, active). However, all callers (anthropic, google_genai, litellm, mcp, openai_agents, pydantic_ai integrations) pass legacy parameters like op=... and origin=... which are not accepted by the streaming API. This will cause a TypeError at runtime when span streaming is enabled.
Also found at:
sentry_sdk/integrations/asgi.py:238-241sentry_sdk/integrations/httpx.py:116-118sentry_sdk/integrations/stdlib.py:175-177
StreamedSpan.set_status() method does not exist, causing AttributeError on SQL errors - `sentry_sdk/integrations/sqlalchemy.py:102`
In the _handle_error function, when a StreamedSpan is detected, the code calls span.set_status(SpanStatus.ERROR). However, the StreamedSpan class does not have a set_status() method - it only has a status property with a setter. This will raise an AttributeError when a SQLAlchemy error occurs in streaming mode, preventing proper error status tracking and potentially breaking error handling.
Also found at:
sentry_sdk/integrations/celery/__init__.py:104-105
NoOpStreamedSpan._get_trace_context() will crash due to missing instance attributes - `sentry_sdk/scope.py:609`
When a NoOpStreamedSpan is set as the scope's active span and scope.get_trace_context() is called, line 609 will invoke NoOpStreamedSpan._get_trace_context(). However, NoOpStreamedSpan does not override this method and inherits it from StreamedSpan. The inherited method accesses self._parent_span_id and self._attributes, which are never initialized in NoOpStreamedSpan.init (it doesn't call super().init()). It also calls self._dynamic_sampling_context() which tries to access self._segment._get_baggage() but _segment is None. This will result in AttributeError crashes when events are captured while a NoOpStreamedSpan is active.
Also found at:
sentry_sdk/traces.py:583
Medium
Spans not closed if Redis command raises exception in async client - `sentry_sdk/integrations/redis/_async_common.py:145-147`
In _sentry_execute_command, if old_execute_command raises an exception, db_span.__exit__() and cache_span.__exit__() are never called because there's no try/finally block. This causes span resources to leak and spans to never be properly closed/sent. The sync version in _sync_common.py correctly uses try/finally (lines 151-160) to ensure spans are always closed.
Also found at:
sentry_sdk/integrations/redis/_async_common.py:147
Low
UnboundLocalError when Redis command fails with cache span active - `sentry_sdk/integrations/redis/_sync_common.py:158`
In sentry_patched_execute_command, if old_execute_command raises an exception and cache_span is set, the finally block will attempt to access value in _set_cache_data, which will raise UnboundLocalError. While capture_internal_exceptions() catches this, it causes spurious internal errors to be logged and masks the intent of the error handling. The async version in _async_common.py has the opposite problem - it doesn't wrap in try/finally, so spans leak on exception.
Duration: 27m 24s · Tokens: 20.7M in / 179.7k out · Cost: $29.65 (+extraction: $0.03, +merge: $0.01, +fix_gate: $0.01)
Annotations
Check failure on line 540 in sentry_sdk/ai/utils.py
sentry-warden / warden: find-bugs
get_start_span_function returns incompatible function signature when streaming is enabled
When span streaming is enabled or the current span is a StreamedSpan, `get_start_span_function()` returns `sentry_sdk.traces.start_span` which only accepts `(name, attributes, parent_span, active)`. However, all callers (anthropic, google_genai, litellm, mcp, openai_agents, pydantic_ai integrations) pass legacy parameters like `op=...` and `origin=...` which are not accepted by the streaming API. This will cause a TypeError at runtime when span streaming is enabled.
Check failure on line 241 in sentry_sdk/integrations/asgi.py
sentry-warden / warden: find-bugs
[5CY-WHD] get_start_span_function returns incompatible function signature when streaming is enabled (additional location)
When span streaming is enabled or the current span is a StreamedSpan, `get_start_span_function()` returns `sentry_sdk.traces.start_span` which only accepts `(name, attributes, parent_span, active)`. However, all callers (anthropic, google_genai, litellm, mcp, openai_agents, pydantic_ai integrations) pass legacy parameters like `op=...` and `origin=...` which are not accepted by the streaming API. This will cause a TypeError at runtime when span streaming is enabled.
Check failure on line 118 in sentry_sdk/integrations/httpx.py
sentry-warden / warden: find-bugs
[5CY-WHD] get_start_span_function returns incompatible function signature when streaming is enabled (additional location)
When span streaming is enabled or the current span is a StreamedSpan, `get_start_span_function()` returns `sentry_sdk.traces.start_span` which only accepts `(name, attributes, parent_span, active)`. However, all callers (anthropic, google_genai, litellm, mcp, openai_agents, pydantic_ai integrations) pass legacy parameters like `op=...` and `origin=...` which are not accepted by the streaming API. This will cause a TypeError at runtime when span streaming is enabled.
Check failure on line 177 in sentry_sdk/integrations/stdlib.py
sentry-warden / warden: find-bugs
[5CY-WHD] get_start_span_function returns incompatible function signature when streaming is enabled (additional location)
When span streaming is enabled or the current span is a StreamedSpan, `get_start_span_function()` returns `sentry_sdk.traces.start_span` which only accepts `(name, attributes, parent_span, active)`. However, all callers (anthropic, google_genai, litellm, mcp, openai_agents, pydantic_ai integrations) pass legacy parameters like `op=...` and `origin=...` which are not accepted by the streaming API. This will cause a TypeError at runtime when span streaming is enabled.
Check failure on line 102 in sentry_sdk/integrations/sqlalchemy.py
sentry-warden / warden: find-bugs
StreamedSpan.set_status() method does not exist, causing AttributeError on SQL errors
In the `_handle_error` function, when a StreamedSpan is detected, the code calls `span.set_status(SpanStatus.ERROR)`. However, the `StreamedSpan` class does not have a `set_status()` method - it only has a `status` property with a setter. This will raise an `AttributeError` when a SQLAlchemy error occurs in streaming mode, preventing proper error status tracking and potentially breaking error handling.
Check failure on line 105 in sentry_sdk/integrations/celery/__init__.py
sentry-warden / warden: find-bugs
[6GR-KXG] StreamedSpan.set_status() method does not exist, causing AttributeError on SQL errors (additional location)
In the `_handle_error` function, when a StreamedSpan is detected, the code calls `span.set_status(SpanStatus.ERROR)`. However, the `StreamedSpan` class does not have a `set_status()` method - it only has a `status` property with a setter. This will raise an `AttributeError` when a SQLAlchemy error occurs in streaming mode, preventing proper error status tracking and potentially breaking error handling.
Check failure on line 609 in sentry_sdk/scope.py
sentry-warden / warden: find-bugs
NoOpStreamedSpan._get_trace_context() will crash due to missing instance attributes
When a NoOpStreamedSpan is set as the scope's active span and scope.get_trace_context() is called, line 609 will invoke NoOpStreamedSpan._get_trace_context(). However, NoOpStreamedSpan does not override this method and inherits it from StreamedSpan. The inherited method accesses self._parent_span_id and self._attributes, which are never initialized in NoOpStreamedSpan.__init__ (it doesn't call super().__init__()). It also calls self._dynamic_sampling_context() which tries to access self._segment._get_baggage() but _segment is None. This will result in AttributeError crashes when events are captured while a NoOpStreamedSpan is active.
Check failure on line 583 in sentry_sdk/traces.py
sentry-warden / warden: find-bugs
[WF5-QNQ] NoOpStreamedSpan._get_trace_context() will crash due to missing instance attributes (additional location)
When a NoOpStreamedSpan is set as the scope's active span and scope.get_trace_context() is called, line 609 will invoke NoOpStreamedSpan._get_trace_context(). However, NoOpStreamedSpan does not override this method and inherits it from StreamedSpan. The inherited method accesses self._parent_span_id and self._attributes, which are never initialized in NoOpStreamedSpan.__init__ (it doesn't call super().__init__()). It also calls self._dynamic_sampling_context() which tries to access self._segment._get_baggage() but _segment is None. This will result in AttributeError crashes when events are captured while a NoOpStreamedSpan is active.
Check warning on line 147 in sentry_sdk/integrations/redis/_async_common.py
sentry-warden / warden: find-bugs
Spans not closed if Redis command raises exception in async client
In `_sentry_execute_command`, if `old_execute_command` raises an exception, `db_span.__exit__()` and `cache_span.__exit__()` are never called because there's no try/finally block. This causes span resources to leak and spans to never be properly closed/sent. The sync version in `_sync_common.py` correctly uses `try/finally` (lines 151-160) to ensure spans are always closed.
Check warning on line 147 in sentry_sdk/integrations/redis/_async_common.py
sentry-warden / warden: find-bugs
[UEP-MCD] Spans not closed if Redis command raises exception in async client (additional location)
In `_sentry_execute_command`, if `old_execute_command` raises an exception, `db_span.__exit__()` and `cache_span.__exit__()` are never called because there's no try/finally block. This causes span resources to leak and spans to never be properly closed/sent. The sync version in `_sync_common.py` correctly uses `try/finally` (lines 151-160) to ensure spans are always closed.