Skip to content

Merge branch 'release/2.52.0a9' into feat/span-first

47b1de2
Select commit
Loading
Failed to load commit list.
Draft

[do not merge] feat: Span streaming & new span API #5551

Merge branch 'release/2.52.0a9' into feat/span-first
47b1de2
Select commit
Loading
Failed to load commit list.
@sentry/warden / warden completed Mar 26, 2026 in 32m 17s

7 issues

High

StreamedSpan.set_status() method does not exist - will raise AttributeError - `sentry_sdk/integrations/sqlalchemy.py:102`

The code calls span.set_status(SpanStatus.ERROR) on a StreamedSpan instance, but StreamedSpan class only has a status property setter, not a set_status() method. This will raise AttributeError: 'StreamedSpan' object has no attribute 'set_status' at runtime when a SQL error occurs in streaming mode. The fix should use span.status = SpanStatus.ERROR instead.

StreamedSpan.set_status() method doesn't exist, causing AttributeError - `sentry_sdk/integrations/sqlalchemy.py:102`

The code calls span.set_status(SpanStatus.ERROR) on a StreamedSpan instance at line 102, but StreamedSpan (defined in sentry_sdk/traces.py) does not have a set_status() method. It only has a status property setter. This will raise an AttributeError: 'StreamedSpan' object has no attribute 'set_status' when SQLAlchemy encounters an error and the span is a StreamedSpan instance. The legacy Span class (from tracing.py) does have set_status(), explaining why the else branch works.

Also found at:

  • sentry_sdk/integrations/httpx.py:117-119

Medium

StreamedSpan missing HTTP status code attribute - `sentry_sdk/integrations/httpx.py:117-119`

For StreamedSpan, only span.status ('ok'/'error') and 'reason' attributes are set, but the actual HTTP status code attribute (http.response.status_code) is not set. In contrast, the legacy Span path uses set_http_status() which sets SPANDATA.HTTP_STATUS_CODE. This results in streamed spans missing the numeric HTTP status code, reducing observability for HTTP client requests.

Also found at:

  • sentry_sdk/integrations/stdlib.py:175-177
Spans not properly closed if Redis command raises exception - `sentry_sdk/integrations/redis/_async_common.py:145`

In _sentry_execute_command, the db_span and cache_span are manually entered with __enter__() but lack try/finally protection around await old_execute_command(). If the Redis command raises an exception, the spans won't be properly closed via __exit__(). This differs from the sync version (_sync_common.py) which correctly uses try/finally. Unclosed spans can cause resource leaks and incorrect span hierarchies in traces.

Also found at:

  • sentry_sdk/integrations/redis/_sync_common.py:158
Spans are leaked on exception in async Redis client execute_command - `sentry_sdk/integrations/redis/_async_common.py:145`

In _sentry_execute_command, spans are manually entered via __enter__() on lines 126 and 145, but the subsequent __exit__() calls (lines 152 and 156) are not wrapped in a try/finally block. If await old_execute_command(self, name, *args, **kwargs) raises an exception, both db_span and cache_span will never be properly closed. This differs from the synchronous version in _sync_common.py which correctly uses try/finally. Unclosed spans can cause memory leaks and incorrect tracing data.

Also found at:

  • sentry_sdk/integrations/redis/_sync_common.py:158
getresponse exception handling doesn't set span error status for StreamedSpan - `sentry_sdk/integrations/stdlib.py:181-185`

When real_getresponse() raises an exception (e.g., network timeout, connection refused), the finally block calls span.end() directly without setting the span status to 'error'. For StreamedSpan, the status defaults to 'ok' (set in traces.py:289), so exceptions will be incorrectly recorded as successful spans. The httpx integration avoids this by using with span_ctx as span: which triggers __exit__ and properly sets error status on exceptions.

Also found at:

  • sentry_sdk/integrations/strawberry.py:226

Low

NoOpStreamedSpan._to_traceparent() appears unreachable - `sentry_sdk/traces.py:692-697`

The new _to_traceparent() method on NoOpStreamedSpan (lines 692-697) is never called. Both call sites that invoke _to_traceparent() have guards: scope.get_traceparent() explicitly skips NoOpStreamedSpan instances via isinstance check, and StreamedSpan._iter_headers() returns early when _segment is None (always true for NoOpStreamedSpan). This appears to be defensive programming for future-proofing, but without test coverage, the intended behavior cannot be verified.

4 skills analyzed
Skill Findings Duration Cost
code-review 3 12m 14s $12.85
find-bugs 4 23m 53s $20.40
skill-scanner 0 27m 36s $6.42
security-review 0 32m 11s $7.98

Duration: 95m 54s · Tokens: 30.2M in / 326.7k out · Cost: $47.74 (+extraction: $0.03, +merge: $0.01, +fix_gate: $0.01, +dedup: $0.03)