[do not merge] feat: Span streaming & new span API #5551
7 issues
find-bugs: Found 7 issues (2 high, 4 medium, 1 low)
High
set_attribute and set_data method dispatch is inverted, causing AttributeError at runtime - `sentry_sdk/integrations/openai_agents/utils.py:55-58`
The conditional logic assigns span.set_attribute when the span is a Span instance, but Span (from sentry_sdk/tracing.py) only has set_data, not set_attribute. Conversely, when the span is a StreamedSpan, it assigns span.set_data, but StreamedSpan (from sentry_sdk/traces.py) only has set_attribute, not set_data. This will cause an AttributeError whenever _record_exception_on_span is called with either span type.
Also found at:
sentry_sdk/integrations/sqlalchemy.py:102
Spans not closed on exception in async Redis execute_command - `sentry_sdk/integrations/redis/_async_common.py:145-156`
When an exception occurs during await old_execute_command() at line 150, the code flow exits immediately without calling db_span.__exit__() or cache_span.__exit__(). This causes spans to never finish, leaking them without sending to Sentry. The sync version in _sync_common.py correctly uses try/finally but the async version lacks this protection, leading to orphaned spans and potential scope corruption.
Medium
Span streaming mode captures HTTP methods that should be excluded - `sentry_sdk/integrations/asgi.py:238-241`
In span streaming mode, when ty is "http" but method is NOT in http_methods_to_capture (e.g., HEAD or OPTIONS requests), the code still creates and starts a span (line 238). In contrast, the legacy non-streaming path correctly returns nullcontext() when these conditions are not met (line 274), avoiding span creation. This causes span streaming mode to trace HTTP methods that users explicitly want excluded, resulting in unwanted spans being sent to Sentry.
HTTP status code attribute missing for StreamedSpan in sync httpx client - `sentry_sdk/integrations/httpx.py:116-118`
When span streaming mode is enabled, the StreamedSpan does not have the http.response.status_code attribute set after the HTTP request completes. The legacy Span.set_http_status() sets SPANDATA.HTTP_STATUS_CODE, but the StreamedSpan path only sets status ('ok'/'error') and 'reason'. This means HTTP status code information (e.g., 200, 404, 500) will not be available in the span attributes for span-streaming users.
Also found at:
sentry_sdk/integrations/httpx.py:199-201
UnboundLocalError when Redis command fails and cache_span is active - `sentry_sdk/integrations/redis/_sync_common.py:158`
In sentry_patched_execute_command, if old_execute_command raises an exception, the finally block attempts to call _set_cache_data(cache_span, self, cache_properties, value), but value was never assigned. This causes an UnboundLocalError. While capture_internal_exceptions() suppresses the error (logging it silently), this masks real Redis failures and causes unexpected log noise in the SDK.
NoOpStreamedSpan created for child spans without scope parameter breaks span hierarchy tracking - `sentry_sdk/scope.py:1302-1308`
When creating NoOpStreamedSpan instances at lines 1302-1304 and 1306-1308 for child spans, the scope=self parameter is omitted, unlike the segment span cases at lines 1265 and 1280 which include it. Without the scope parameter, NoOpStreamedSpan._start() returns early and doesn't set the span on the scope, and _end() doesn't restore the previous span. This breaks the scope's span tracking hierarchy for subsequent span operations.
Low
Duplicate constant definitions for BAGGAGE_HEADER_NAME and SENTRY_TRACE_HEADER_NAME - `sentry_sdk/traces.py:41-42`
The constants BAGGAGE_HEADER_NAME and SENTRY_TRACE_HEADER_NAME are defined twice - first in the newly added lines (38-40 area) and again in the existing code (lines 45-46 shown in context). While Python will simply use the last definition, this is clearly unintended code duplication that indicates a merge error or copy-paste mistake. The duplicate definitions waste memory and create confusion for maintainers.
Duration: 14m 18s · Tokens: 13.7M in / 131.8k out · Cost: $20.70 (+extraction: $0.02, +merge: $0.00, +fix_gate: $0.02)
Annotations
Check failure on line 58 in sentry_sdk/integrations/openai_agents/utils.py
sentry-warden / warden: find-bugs
set_attribute and set_data method dispatch is inverted, causing AttributeError at runtime
The conditional logic assigns `span.set_attribute` when the span is a `Span` instance, but `Span` (from `sentry_sdk/tracing.py`) only has `set_data`, not `set_attribute`. Conversely, when the span is a `StreamedSpan`, it assigns `span.set_data`, but `StreamedSpan` (from `sentry_sdk/traces.py`) only has `set_attribute`, not `set_data`. This will cause an `AttributeError` whenever `_record_exception_on_span` is called with either span type.
Check failure on line 102 in sentry_sdk/integrations/sqlalchemy.py
sentry-warden / warden: find-bugs
[ZYQ-ZDV] set_attribute and set_data method dispatch is inverted, causing AttributeError at runtime (additional location)
The conditional logic assigns `span.set_attribute` when the span is a `Span` instance, but `Span` (from `sentry_sdk/tracing.py`) only has `set_data`, not `set_attribute`. Conversely, when the span is a `StreamedSpan`, it assigns `span.set_data`, but `StreamedSpan` (from `sentry_sdk/traces.py`) only has `set_attribute`, not `set_data`. This will cause an `AttributeError` whenever `_record_exception_on_span` is called with either span type.
Check failure on line 156 in sentry_sdk/integrations/redis/_async_common.py
sentry-warden / warden: find-bugs
Spans not closed on exception in async Redis execute_command
When an exception occurs during `await old_execute_command()` at line 150, the code flow exits immediately without calling `db_span.__exit__()` or `cache_span.__exit__()`. This causes spans to never finish, leaking them without sending to Sentry. The sync version in `_sync_common.py` correctly uses `try/finally` but the async version lacks this protection, leading to orphaned spans and potential scope corruption.
Check warning on line 241 in sentry_sdk/integrations/asgi.py
sentry-warden / warden: find-bugs
Span streaming mode captures HTTP methods that should be excluded
In span streaming mode, when `ty` is "http" but `method` is NOT in `http_methods_to_capture` (e.g., HEAD or OPTIONS requests), the code still creates and starts a span (line 238). In contrast, the legacy non-streaming path correctly returns `nullcontext()` when these conditions are not met (line 274), avoiding span creation. This causes span streaming mode to trace HTTP methods that users explicitly want excluded, resulting in unwanted spans being sent to Sentry.
Check warning on line 118 in sentry_sdk/integrations/httpx.py
sentry-warden / warden: find-bugs
HTTP status code attribute missing for StreamedSpan in sync httpx client
When span streaming mode is enabled, the StreamedSpan does not have the `http.response.status_code` attribute set after the HTTP request completes. The legacy Span.set_http_status() sets SPANDATA.HTTP_STATUS_CODE, but the StreamedSpan path only sets status ('ok'/'error') and 'reason'. This means HTTP status code information (e.g., 200, 404, 500) will not be available in the span attributes for span-streaming users.
Check warning on line 201 in sentry_sdk/integrations/httpx.py
sentry-warden / warden: find-bugs
[RYG-KSM] HTTP status code attribute missing for StreamedSpan in sync httpx client (additional location)
When span streaming mode is enabled, the StreamedSpan does not have the `http.response.status_code` attribute set after the HTTP request completes. The legacy Span.set_http_status() sets SPANDATA.HTTP_STATUS_CODE, but the StreamedSpan path only sets status ('ok'/'error') and 'reason'. This means HTTP status code information (e.g., 200, 404, 500) will not be available in the span attributes for span-streaming users.
Check warning on line 158 in sentry_sdk/integrations/redis/_sync_common.py
sentry-warden / warden: find-bugs
UnboundLocalError when Redis command fails and cache_span is active
In `sentry_patched_execute_command`, if `old_execute_command` raises an exception, the `finally` block attempts to call `_set_cache_data(cache_span, self, cache_properties, value)`, but `value` was never assigned. This causes an `UnboundLocalError`. While `capture_internal_exceptions()` suppresses the error (logging it silently), this masks real Redis failures and causes unexpected log noise in the SDK.
Check warning on line 1308 in sentry_sdk/scope.py
sentry-warden / warden: find-bugs
NoOpStreamedSpan created for child spans without scope parameter breaks span hierarchy tracking
When creating NoOpStreamedSpan instances at lines 1302-1304 and 1306-1308 for child spans, the `scope=self` parameter is omitted, unlike the segment span cases at lines 1265 and 1280 which include it. Without the scope parameter, NoOpStreamedSpan._start() returns early and doesn't set the span on the scope, and _end() doesn't restore the previous span. This breaks the scope's span tracking hierarchy for subsequent span operations.