[do not merge] feat: Span streaming & new span API #5551
12 issues
High
set_attribute method does not exist on Span class, will raise AttributeError - `sentry_sdk/integrations/openai_agents/utils.py:55-58`
The code assigns set_on_span = span.set_attribute when the span is an instance of Span, but the Span class (in sentry_sdk/tracing.py) does not have a set_attribute method. This will cause an AttributeError at runtime when _record_exception_on_span is called with a legacy Span object. The logic appears to be inverted - Span has set_data() while StreamedSpan has set_attribute().
Also found at:
sentry_sdk/integrations/sqlalchemy.py:102
Spans not properly closed on exception in async Redis command execution - `sentry_sdk/integrations/redis/_async_common.py:145-156`
In _sentry_execute_command, the db_span and cache_span are entered via __enter__() but their __exit__() calls (lines 152, 156) are not protected by a try/finally block. If old_execute_command raises an exception, the spans will never be closed, causing span leaks and incorrect timing data. The sync version in _sync_common.py correctly uses try/finally for this pattern.
Also found at:
sentry_sdk/integrations/redis/_sync_common.py:152
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
Streaming mode ignores http_methods_to_capture filter, creating spans for filtered methods - `sentry_sdk/integrations/asgi.py:238-241`
In the span streaming branch (lines 218-241), a span is always created via start_span() at line 238, even when ty == 'http' and the method is not in http_methods_to_capture. In contrast, the legacy branch correctly sets transaction = None and uses nullcontext() to skip span creation for filtered methods. This causes unintended spans to be created and sent for HTTP methods that should be excluded.
Also found at:
sentry_sdk/integrations/graphene.py:174-175
StreamedSpan is missing HTTP status code attribute - `sentry_sdk/integrations/httpx.py:116-118`
When using span streaming mode, the code sets span.status and span.set_attribute("reason", ...) but doesn't set the HTTP status code attribute (SPANDATA.HTTP_STATUS_CODE). The legacy Span.set_http_status() method sets both the status and http.response.status_code. This means StreamedSpans will be missing important HTTP status code telemetry data that the legacy spans include.
Also found at:
sentry_sdk/integrations/httpx.py:199-201sentry_sdk/integrations/stdlib.py:175-177
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.
...and 2 more
4 skills analyzed
| Skill | Findings | Duration | Cost |
|---|---|---|---|
| code-review | 5 | 26m 8s | $14.33 |
| find-bugs | 7 | 14m 18s | $20.66 |
| skill-scanner | 0 | 28m 17s | $6.69 |
| security-review | 0 | 33m 47s | $8.38 |
Duration: 102m 30s · Tokens: 31.6M in / 343.8k out · Cost: $50.17 (+extraction: $0.04, +merge: $0.01, +fix_gate: $0.04, +dedup: $0.03)