[do not merge] feat: Span streaming & new span API #5551
8 issues
High
UnboundLocalError when Redis command fails with cache span active - `sentry_sdk/integrations/redis/_sync_common.py:158`
When old_execute_command raises an exception and a cache_span exists, the finally block attempts to access value in _set_cache_data(cache_span, self, cache_properties, value) before it's assigned. This causes an UnboundLocalError that replaces the original Redis exception, making it impossible to debug the actual failure cause.
Also found at:
sentry_sdk/integrations/redis/_async_common.py:145-147
StreamedSpan.set_status() method does not exist - will raise AttributeError at runtime - `sentry_sdk/integrations/sqlalchemy.py:102`
The code calls span.set_status(SpanStatus.ERROR) when the span is a StreamedSpan instance (line 102). However, the StreamedSpan class in sentry_sdk/traces.py does not have a set_status() method - it only has a status property setter. The legacy Span class has set_status() (line 600 in tracing.py), but StreamedSpan does not, so this will raise an AttributeError at runtime when handling SQL errors in streaming mode.
StreamedSpan.set_status() method does not exist - will raise AttributeError - `sentry_sdk/integrations/sqlalchemy.py:102`
The code at line 102 calls span.set_status(SpanStatus.ERROR) on a StreamedSpan object, but StreamedSpan class does not have a set_status() method. Unlike the legacy Span class which has set_status(), StreamedSpan only provides a status property setter. This will cause an AttributeError at runtime when span streaming mode is enabled and a SQL error occurs.
Also found at:
sentry_sdk/integrations/strawberry.py:226
Medium
Missing test coverage for Celery integration with span streaming mode
The changes introduce significant branching logic to handle both legacy and span streaming modes in apply_async and _wrap_tracer functions. However, there are no tests in tests/integrations/celery/ that exercise the new span streaming code paths (checked by searching for trace_lifecycle and span_streaming patterns). Without integration tests covering the span streaming mode, regressions in Celery task tracing could go undetected.
HTTP status code attribute not set for StreamedSpan - `sentry_sdk/integrations/stdlib.py:175-177`
When handling HTTP responses with StreamedSpan, the code sets span.status and the reason attribute but does not set the HTTP status code attribute (SPANDATA.HTTP_STATUS_CODE). The legacy path correctly calls set_http_status() which sets http.response.status_code. This inconsistency means that HTTP response status codes will be missing from spans in streaming mode, reducing observability of HTTP client calls.
StreamedSpan missing HTTP status code attribute in sync httpx client - `sentry_sdk/integrations/httpx.py:116-118`
When using span streaming mode, the HTTP response status code is not captured as an attribute. Lines 116-118 set span.status to 'ok' or 'error' and add a 'reason' attribute, but unlike the legacy set_http_status() path (line 120) or the ASGI integration (which sets http.response.status_code), the actual numeric status code is never recorded. This causes data loss - users cannot see the specific HTTP status code (e.g., 404 vs 500) in their telemetry.
Also found at:
sentry_sdk/integrations/httpx.py:199-201sentry_sdk/integrations/stdlib.py:175-177
Async Redis spans leak when command raises exception - `sentry_sdk/integrations/redis/_async_common.py:145-156`
In _sentry_execute_command, the db_span and cache_span are manually entered with __enter__() but are not protected by a try/finally block. If old_execute_command(self, name, *args, **kwargs) raises an exception, __exit__() is never called on either span. This causes span leaks and leaves the scope in an inconsistent state (the span remains as the active span on the scope). The sync version in _sync_common.py correctly uses try/finally for this (lines 151-160).
Also found at:
sentry_sdk/integrations/redis/_sync_common.py:158
Low
Redundant Pattern import creates confusion and potential shadowing - `sentry_sdk/tracing_utils.py:13`
The code imports Pattern unconditionally from typing on line 13, then immediately tries to import it again from re in the try/except block on lines 15-19. This results in the re.Pattern import (when successful in Python 3.7+) shadowing the typing.Pattern import. While not a runtime error, this redundancy is confusing and the unconditional typing import on line 13 serves no purpose.
4 skills analyzed
| Skill | Findings | Duration | Cost |
|---|---|---|---|
| code-review | 5 | 12m 46s | $13.00 |
| find-bugs | 3 | 24m 1s | $20.27 |
| skill-scanner | 0 | 32m 41s | $6.14 |
| security-review | 0 | 28m 1s | $7.44 |
Duration: 97m 29s · Tokens: 30.1M in / 331.7k out · Cost: $46.96 (+extraction: $0.05, +merge: $0.01, +fix_gate: $0.01, +dedup: $0.03)