[do not merge] feat: Span streaming & new span API #5317
19 issues
High
API signature mismatch causes runtime TypeError when streaming is enabled - `sentry_sdk/ai/utils.py:542`
The get_start_span_function() returns sentry_sdk.traces.start_span when streaming mode is enabled, but this function only accepts name, attributes, and parent_span parameters. All callers (in litellm, anthropic, google_genai, langchain, mcp, pydantic_ai, openai_agents integrations) pass additional kwargs like op=... and origin=... which will cause TypeError: start_span() got an unexpected keyword argument 'op'. This breaks all AI integrations when span streaming is enabled.
Also found at:
sentry_sdk/integrations/celery/__init__.py:330-337
UnboundLocalError when Redis command raises exception - `sentry_sdk/integrations/redis/_sync_common.py:148`
In the finally block (line 143-150), the code references value when calling _set_cache_data(cache_span, self, cache_properties, value) on line 148. However, if old_execute_command on line 142 raises an exception, value is never assigned, causing an UnboundLocalError. This will crash the Redis instrumentation when any Redis operation fails, breaking error handling.
Also found at:
sentry_sdk/integrations/redis/_async_common.py:135-146
StreamedSpan created but never started before finish() is called - `sentry_sdk/integrations/strawberry.py:192-208`
The graphql_span is created via sentry_sdk.traces.start_span() in span streaming mode (line 192) but is never started before yield. When self.graphql_span.finish() is called at line 234, it will trigger __exit__ which accesses self._context_manager_state - an attribute that is only set by __enter__/start(). This will cause an AttributeError: 'StreamedSpan' object has no attribute '_context_manager_state' at runtime when span streaming is enabled.
Also found at:
sentry_sdk/integrations/strawberry.py:239-246sentry_sdk/integrations/strawberry.py:261-266
Type annotation `dict[str, Any]` incompatible with Python 3.6-3.8 - `sentry_sdk/tracing_utils.py:478`
The type annotation dict[str, Any] uses PEP 585 syntax which is only available in Python 3.9+. However, the SDK supports Python >= 3.6 (as declared in setup.py). On Python 3.6-3.8, this will cause a TypeError at runtime when the annotation is evaluated. The rest of the codebase consistently uses Dict[str, Any] from the typing module (e.g., lines 384, 453, 536 in this file).
API incompatibility: sentry_sdk.traces.start_span doesn't accept keyword arguments used by callers - `sentry_sdk/ai/utils.py:542`
When span streaming is enabled, get_start_span_function() returns sentry_sdk.traces.start_span, which has signature start_span(name: str, attributes: Optional[Attributes] = None, parent_span: Optional[StreamedSpan] = None). However, all existing callers (anthropic.py, litellm.py, google_genai, langchain.py, mcp.py, openai_agents, pydantic_ai) invoke the returned function with keyword arguments op=..., name=..., origin=... which the new API doesn't accept. This will cause a TypeError at runtime when span streaming is enabled.
StreamedSpan.finish() fails when span not used as context manager - `sentry_sdk/integrations/strawberry.py:192-234`
In streaming mode, spans are created via sentry_sdk.traces.start_span() but are not used as context managers nor explicitly started via .start(). When .finish() is called later, it invokes __exit__() which tries to access self._context_manager_state that was never set (since __enter__ was never called). This causes an AttributeError that is silently caught by capture_internal_exceptions(), preventing the span from being properly ended and sent to Sentry.
Also found at:
sentry_sdk/traces.py:698
Medium
Source code information lost for StreamedSpan due to modification after span.end() - `sentry_sdk/integrations/stdlib.py:183-189`
In streaming mode, span.end() captures and queues the span for sending via scope._capture_span(self) before returning. The add_http_request_source() call on line 189 happens after span.end() completes, so any attributes it sets (source code information like code.lineno, code.filepath, etc.) will not be included in the sent span data. This is different from the legacy path where spans are only sent when the transaction finishes.
NoOpStreamedSpan records lost event even when span was never active - `sentry_sdk/traces.py:714-718`
In NoOpStreamedSpan.__exit__, the lost event is recorded via transport.record_lost_event() (lines 714-718) before checking if self._scope is None (line 720). When a NoOpStreamedSpan is created with scope=None, the __enter__ method returns early without doing anything, but __exit__ will still record a lost event. This leads to incorrect metrics, as spans that were never 'started' (because they had no scope) will be counted as lost.
StreamedSpan not closed on error in Anthropic integration - `sentry_sdk/integrations/anthropic.py:610-612`
When using the new span streaming mode (_experiments={"trace_lifecycle": "stream"}), if an exception occurs during Anthropic API calls, the StreamedSpan created in _sentry_patched_create_common will not be properly closed. The span is started via span.__enter__() but the error cleanup in the finally block only handles legacy Span objects (via isinstance(span, Span)), leaving StreamedSpan objects open. This results in spans without end timestamps and potential data loss.
Also found at:
sentry_sdk/integrations/redis/_async_common.py:135-146sentry_sdk/integrations/stdlib.py:182-186
Control flow exceptions (Retry, Ignore, Reject) incorrectly marked as ERROR - `sentry_sdk/integrations/celery/__init__.py:105`
When a Celery task raises control flow exceptions (Retry, Ignore, Reject), the _set_status("aborted") call sets SpanStatus.ERROR for StreamedSpan. However, these are not actual errors - they are expected control flow mechanisms in Celery. A Retry indicates the task will be retried, Ignore means the result is intentionally ignored, and Reject means the task was rejected. Marking these as ERROR may cause misleading error counts in monitoring dashboards.
...and 9 more
4 skills analyzed
| Skill | Findings | Duration | Cost |
|---|---|---|---|
| code-review | 8 | 34m 26s | $18.80 |
| find-bugs | 11 | 18m 48s | $31.71 |
| skill-scanner | 0 | 43m 12s | $7.76 |
| security-review | 0 | 38m 22s | $11.75 |
Duration: 134m 48s · Tokens: 48.5M in / 467.0k out · Cost: $70.11 (+extraction: $0.04, +merge: $0.01, +dedup: $0.03)