[do not merge] feat: Span streaming & new span API #5551
12 issues
High
Accessing undefined `value` variable in finally block causes NameError - `sentry_sdk/integrations/redis/_sync_common.py:158`
When old_execute_command raises an exception at line 152, the value variable is never assigned. The finally block always executes, including line 158 which passes value to _set_cache_data. This causes an UnboundLocalError: local variable 'value' referenced before assignment. While wrapped in capture_internal_exceptions, this silently loses the ability to record cache span data on errors.
Also found at:
sentry_sdk/integrations/redis/_async_common.py:145-147
StreamedSpan lacks set_status() method causing AttributeError at runtime - `sentry_sdk/integrations/sqlalchemy.py:102`
The code calls span.set_status(SpanStatus.ERROR) on a StreamedSpan, but StreamedSpan class does not have a set_status() method. It only has a status property setter (lines 426-437 in traces.py). The legacy Span class in tracing.py has set_status() at line 600, but the new StreamedSpan does not. This will raise an AttributeError at runtime when SQLAlchemy query errors occur in streaming mode.
NoOpStreamedSpan._get_trace_context() will raise AttributeError due to _segment being None - `sentry_sdk/traces.py:583`
Setting self._segment = None at line 583 in NoOpStreamedSpan.__init__ will cause a runtime error when _get_trace_context() is called. The inherited _get_trace_context() method (line 537-552) calls self._dynamic_sampling_context(), which directly accesses self._segment._get_baggage() without a null check. This method is called from scope.get_trace_context() when the active span is a NoOpStreamedSpan, resulting in AttributeError: 'NoneType' object has no attribute '_get_baggage'.
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
Span streaming path creates spans for HTTP methods that should be ignored - `sentry_sdk/integrations/asgi.py:238-241`
In the span streaming path (lines 218-241), when ty is http but the method is not in self.http_methods_to_capture, the code still creates a span (line 238-240) without setting sentry.op or calling trace propagation functions. The legacy path correctly uses nullcontext() when the HTTP method should not be captured. This causes unintended spans to be created for requests like OPTIONS that are typically excluded.
HTTP status code not recorded on StreamedSpan - `sentry_sdk/integrations/httpx.py:116-118`
When span streaming is enabled, the HTTP response status code is not being recorded as an attribute on the span. The legacy set_http_status() method sets SPANDATA.HTTP_STATUS_CODE (http.response.status_code), but the StreamedSpan code path only sets span.status and reason. This means http.response.status_code will be missing from HTTP spans in streaming mode, causing data loss and inconsistent span attributes between streaming and non-streaming modes.
Also found at:
sentry_sdk/integrations/httpx.py:199-201sentry_sdk/integrations/stdlib.py:175-177
NoOpSpan may return formatted traceparent instead of empty string via private method alias - `sentry_sdk/scope.py:585`
The change from to_traceparent() to _to_traceparent() may alter behavior for NoOpSpan instances. NoOpSpan overrides to_traceparent() to return an empty string, but inherits _to_traceparent from Span which is bound to Span.to_traceparent. When a NoOpSpan is active on the scope, get_traceparent() may now return a formatted traceparent string instead of empty string, potentially causing unintended trace propagation.
Missing scope parameter in NoOpStreamedSpan for ignored child spans - `sentry_sdk/scope.py:1286-1288`
When creating a NoOpStreamedSpan for an ignored child span at line 1287, the scope=self parameter is missing. In contrast, when creating NoOpStreamedSpan for ignored segment spans (line 1250), the scope parameter is passed. Without the scope, _start() returns early and doesn't set the span on the scope, and _end() won't restore the previous span, potentially leaving the scope's span in an inconsistent state.
Also found at:
sentry_sdk/scope.py:1289-1292
...and 2 more
4 skills analyzed
| Skill | Findings | Duration | Cost |
|---|---|---|---|
| code-review | 7 | 42m 42s | $18.37 |
| find-bugs | 5 | 27m 24s | $29.61 |
| skill-scanner | 0 | 44m 42s | $7.80 |
| security-review | 0 | 7m 58s | $10.51 |
Duration: 122m 45s · Tokens: 42.8M in / 444.2k out · Cost: $66.41 (+extraction: $0.05, +merge: $0.01, +fix_gate: $0.02, +dedup: $0.03)