Skip to content

cleanup

cc51564
Select commit
Loading
Failed to load commit list.
Sign in for the full log view
Draft

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

cleanup
cc51564
Select commit
Loading
Failed to load commit list.
GitHub Actions / warden: find-bugs completed Mar 11, 2026 in 40m 13s

10 issues

find-bugs: Found 10 issues (2 high, 6 medium, 2 low)

High

UnboundLocalError when Redis command raises exception - `sentry_sdk/integrations/redis/_sync_common.py:158`

In the finally block (lines 153-160), _set_cache_data(cache_span, self, cache_properties, value) references value which is only assigned if old_execute_command succeeds. If the Redis command raises an exception, value is never assigned, causing an UnboundLocalError in the finally block. This crashes the exception handling and masks the original Redis exception, breaking error propagation to callers.

Also found at:

  • sentry_sdk/integrations/redis/_async_common.py:126-145
Calling _get_trace_context() on NoOpStreamedSpan will crash with AttributeError - `sentry_sdk/scope.py:611`

When span streaming mode is enabled and a span is sampled out, a NoOpStreamedSpan is created and set as the active span on the scope. The NoOpStreamedSpan class sets self._segment = None but does not override _get_trace_context() or _dynamic_sampling_context(). When scope.get_trace_context() is called (line 611), it invokes self._span._get_trace_context(). For NoOpStreamedSpan, this inherits from StreamedSpan which calls self._dynamic_sampling_context(), which in turn calls self._segment._get_baggage(). Since _segment is None, this raises AttributeError: 'NoneType' object has no attribute '_get_baggage'.

Also found at:

  • sentry_sdk/traces.py:586
  • sentry_sdk/integrations/strawberry.py:226

Medium

StreamedSpan error cleanup missing - spans never closed when errors occur in streaming mode - `sentry_sdk/integrations/anthropic.py:572-574`

The change from span is not None to isinstance(span, Span) excludes StreamedSpan from the error cleanup logic. When an exception occurs in the Anthropic API call while using span streaming mode, the StreamedSpan will have its status set to SpanStatus.ERROR by set_span_errored(), but the span's __exit__ method is never called. This results in: (1) the span remaining as the current span on the scope (scope pollution), (2) the span never being sent to Sentry since _end() is never called, and (3) potential memory leaks.

Also found at:

  • sentry_sdk/integrations/anthropic.py:610
Span streaming mode ignores http_methods_to_capture filter - `sentry_sdk/integrations/asgi.py:222-241`

In span streaming mode, when ty is 'http' but the method is NOT in http_methods_to_capture, the code falls through to always create a span (lines 238-240). In non-streaming mode, transaction remains None and no span is created. This causes unwanted tracing of HTTP methods that should be ignored (e.g., OPTIONS, HEAD), creating noise and potentially impacting performance.

Span error status not set when exception occurs in GraphQL operations with span streaming - `sentry_sdk/integrations/graphene.py:168-174`

The graphql_span context manager manually calls _graphql_span.end() in a finally block instead of using the span as a context manager (with with _graphql_span:). When an exception occurs during the yield (i.e., during GraphQL execution), the StreamedSpan.__exit__ method is not invoked, which means the span's status is never set to SpanStatus.ERROR. This causes telemetry to incorrectly report successful spans even when the operation failed with an exception.

Missing HTTP status code attribute in StreamedSpan for sync httpx client - `sentry_sdk/integrations/httpx.py:116-118`

When span streaming mode is enabled, the HTTP status code attribute (http.response.status_code) is never set on the span. The code sets span.status and span.set_attribute("reason", ...) but omits span.set_attribute(SPANDATA.HTTP_STATUS_CODE, rv.status_code). This contrasts with the legacy Span path which calls set_http_status() that sets this attribute. This results in incomplete HTTP span data in streaming mode.

Also found at:

  • sentry_sdk/integrations/httpx.py:199-201
  • sentry_sdk/integrations/stdlib.py:175-177
set_transaction_name skips updating transaction_info for NoOpStreamedSpan - `sentry_sdk/scope.py:831`

In set_transaction_name, when self._span is a NoOpStreamedSpan, the method returns early at line 831, which prevents self._transaction_info["source"] from being updated (lines 845-846). This is scope-level data that should still be set regardless of the span type. Events captured while this scope is active will be missing the transaction source in their transaction_info, causing inconsistent metadata in Sentry.

Dict rule with unrecognized keys in ignore_spans matches all spans - `sentry_sdk/tracing_utils.py:1601-1620`

In is_ignored_span(), when a rule dict contains keys other than 'name' or 'attributes' (e.g., {"foo": "bar"}), both name_matches and attributes_match default to True and are never updated. This causes such rules to match every span, which is almost certainly not the user's intent. While TypedDict provides static type checking, Python doesn't enforce these constraints at runtime.

Low

Incorrect span name 'subprocess popen' should be 'subprocess wait' - `sentry_sdk/integrations/stdlib.py:322`

In sentry_patched_popen_wait, the span name is set to 'subprocess popen' when using span streaming mode (line 322), but this function patches subprocess.Popen.wait and sets the operation to OP.SUBPROCESS_WAIT. The name should be 'subprocess wait' to match the operation and be consistent with the pattern used in sentry_patched_popen_communicate which correctly uses 'subprocess communicate'. This causes incorrect span naming in telemetry data.

Unused import: SegmentSource is imported but never used - `tests/tracing/test_span_streaming.py:10`

The SegmentSource class is imported from sentry_sdk.traces but is never used anywhere in this test file. While not a bug or security issue, this creates unnecessary code complexity and can confuse future maintainers about whether the import was meant to be used in a test that was forgotten.


Duration: 39m 57s · Tokens: 27.8M in / 221.7k out · Cost: $34.17 (+extraction: $0.03, +merge: $0.01, +fix_gate: $0.02)

Annotations

Check failure on line 158 in sentry_sdk/integrations/redis/_sync_common.py

See this annotation in the file changed.

@github-actions github-actions / warden: find-bugs

UnboundLocalError when Redis command raises exception

In the `finally` block (lines 153-160), `_set_cache_data(cache_span, self, cache_properties, value)` references `value` which is only assigned if `old_execute_command` succeeds. If the Redis command raises an exception, `value` is never assigned, causing an `UnboundLocalError` in the finally block. This crashes the exception handling and masks the original Redis exception, breaking error propagation to callers.

Check failure on line 145 in sentry_sdk/integrations/redis/_async_common.py

See this annotation in the file changed.

@github-actions github-actions / warden: find-bugs

[384-RKF] UnboundLocalError when Redis command raises exception (additional location)

In the `finally` block (lines 153-160), `_set_cache_data(cache_span, self, cache_properties, value)` references `value` which is only assigned if `old_execute_command` succeeds. If the Redis command raises an exception, `value` is never assigned, causing an `UnboundLocalError` in the finally block. This crashes the exception handling and masks the original Redis exception, breaking error propagation to callers.

Check failure on line 611 in sentry_sdk/scope.py

See this annotation in the file changed.

@github-actions github-actions / warden: find-bugs

Calling _get_trace_context() on NoOpStreamedSpan will crash with AttributeError

When span streaming mode is enabled and a span is sampled out, a `NoOpStreamedSpan` is created and set as the active span on the scope. The `NoOpStreamedSpan` class sets `self._segment = None` but does not override `_get_trace_context()` or `_dynamic_sampling_context()`. When `scope.get_trace_context()` is called (line 611), it invokes `self._span._get_trace_context()`. For `NoOpStreamedSpan`, this inherits from `StreamedSpan` which calls `self._dynamic_sampling_context()`, which in turn calls `self._segment._get_baggage()`. Since `_segment` is `None`, this raises `AttributeError: 'NoneType' object has no attribute '_get_baggage'`.

Check failure on line 586 in sentry_sdk/traces.py

See this annotation in the file changed.

@github-actions github-actions / warden: find-bugs

[KZP-Z94] Calling _get_trace_context() on NoOpStreamedSpan will crash with AttributeError (additional location)

When span streaming mode is enabled and a span is sampled out, a `NoOpStreamedSpan` is created and set as the active span on the scope. The `NoOpStreamedSpan` class sets `self._segment = None` but does not override `_get_trace_context()` or `_dynamic_sampling_context()`. When `scope.get_trace_context()` is called (line 611), it invokes `self._span._get_trace_context()`. For `NoOpStreamedSpan`, this inherits from `StreamedSpan` which calls `self._dynamic_sampling_context()`, which in turn calls `self._segment._get_baggage()`. Since `_segment` is `None`, this raises `AttributeError: 'NoneType' object has no attribute '_get_baggage'`.

Check failure on line 226 in sentry_sdk/integrations/strawberry.py

See this annotation in the file changed.

@github-actions github-actions / warden: find-bugs

[KZP-Z94] Calling _get_trace_context() on NoOpStreamedSpan will crash with AttributeError (additional location)

When span streaming mode is enabled and a span is sampled out, a `NoOpStreamedSpan` is created and set as the active span on the scope. The `NoOpStreamedSpan` class sets `self._segment = None` but does not override `_get_trace_context()` or `_dynamic_sampling_context()`. When `scope.get_trace_context()` is called (line 611), it invokes `self._span._get_trace_context()`. For `NoOpStreamedSpan`, this inherits from `StreamedSpan` which calls `self._dynamic_sampling_context()`, which in turn calls `self._segment._get_baggage()`. Since `_segment` is `None`, this raises `AttributeError: 'NoneType' object has no attribute '_get_baggage'`.

Check warning on line 574 in sentry_sdk/integrations/anthropic.py

See this annotation in the file changed.

@github-actions github-actions / warden: find-bugs

StreamedSpan error cleanup missing - spans never closed when errors occur in streaming mode

The change from `span is not None` to `isinstance(span, Span)` excludes `StreamedSpan` from the error cleanup logic. When an exception occurs in the Anthropic API call while using span streaming mode, the `StreamedSpan` will have its status set to `SpanStatus.ERROR` by `set_span_errored()`, but the span's `__exit__` method is never called. This results in: (1) the span remaining as the current span on the scope (scope pollution), (2) the span never being sent to Sentry since `_end()` is never called, and (3) potential memory leaks.

Check warning on line 610 in sentry_sdk/integrations/anthropic.py

See this annotation in the file changed.

@github-actions github-actions / warden: find-bugs

[VTM-7BA] StreamedSpan error cleanup missing - spans never closed when errors occur in streaming mode (additional location)

The change from `span is not None` to `isinstance(span, Span)` excludes `StreamedSpan` from the error cleanup logic. When an exception occurs in the Anthropic API call while using span streaming mode, the `StreamedSpan` will have its status set to `SpanStatus.ERROR` by `set_span_errored()`, but the span's `__exit__` method is never called. This results in: (1) the span remaining as the current span on the scope (scope pollution), (2) the span never being sent to Sentry since `_end()` is never called, and (3) potential memory leaks.

Check warning on line 241 in sentry_sdk/integrations/asgi.py

See this annotation in the file changed.

@github-actions github-actions / warden: find-bugs

Span streaming mode ignores http_methods_to_capture filter

In span streaming mode, when `ty` is 'http' but the method is NOT in `http_methods_to_capture`, the code falls through to always create a span (lines 238-240). In non-streaming mode, `transaction` remains `None` and no span is created. This causes unwanted tracing of HTTP methods that should be ignored (e.g., OPTIONS, HEAD), creating noise and potentially impacting performance.

Check warning on line 174 in sentry_sdk/integrations/graphene.py

See this annotation in the file changed.

@github-actions github-actions / warden: find-bugs

Span error status not set when exception occurs in GraphQL operations with span streaming

The `graphql_span` context manager manually calls `_graphql_span.end()` in a `finally` block instead of using the span as a context manager (with `with _graphql_span:`). When an exception occurs during the `yield` (i.e., during GraphQL execution), the `StreamedSpan.__exit__` method is not invoked, which means the span's status is never set to `SpanStatus.ERROR`. This causes telemetry to incorrectly report successful spans even when the operation failed with an exception.

Check warning on line 118 in sentry_sdk/integrations/httpx.py

See this annotation in the file changed.

@github-actions github-actions / warden: find-bugs

Missing HTTP status code attribute in StreamedSpan for sync httpx client

When span streaming mode is enabled, the HTTP status code attribute (`http.response.status_code`) is never set on the span. The code sets `span.status` and `span.set_attribute("reason", ...)` but omits `span.set_attribute(SPANDATA.HTTP_STATUS_CODE, rv.status_code)`. This contrasts with the legacy `Span` path which calls `set_http_status()` that sets this attribute. This results in incomplete HTTP span data in streaming mode.

Check warning on line 201 in sentry_sdk/integrations/httpx.py

See this annotation in the file changed.

@github-actions github-actions / warden: find-bugs

[BZ3-QGD] Missing HTTP status code attribute in StreamedSpan for sync httpx client (additional location)

When span streaming mode is enabled, the HTTP status code attribute (`http.response.status_code`) is never set on the span. The code sets `span.status` and `span.set_attribute("reason", ...)` but omits `span.set_attribute(SPANDATA.HTTP_STATUS_CODE, rv.status_code)`. This contrasts with the legacy `Span` path which calls `set_http_status()` that sets this attribute. This results in incomplete HTTP span data in streaming mode.

Check warning on line 177 in sentry_sdk/integrations/stdlib.py

See this annotation in the file changed.

@github-actions github-actions / warden: find-bugs

[BZ3-QGD] Missing HTTP status code attribute in StreamedSpan for sync httpx client (additional location)

When span streaming mode is enabled, the HTTP status code attribute (`http.response.status_code`) is never set on the span. The code sets `span.status` and `span.set_attribute("reason", ...)` but omits `span.set_attribute(SPANDATA.HTTP_STATUS_CODE, rv.status_code)`. This contrasts with the legacy `Span` path which calls `set_http_status()` that sets this attribute. This results in incomplete HTTP span data in streaming mode.

Check warning on line 831 in sentry_sdk/scope.py

See this annotation in the file changed.

@github-actions github-actions / warden: find-bugs

set_transaction_name skips updating transaction_info for NoOpStreamedSpan

In `set_transaction_name`, when `self._span` is a `NoOpStreamedSpan`, the method returns early at line 831, which prevents `self._transaction_info["source"]` from being updated (lines 845-846). This is scope-level data that should still be set regardless of the span type. Events captured while this scope is active will be missing the transaction source in their `transaction_info`, causing inconsistent metadata in Sentry.

Check warning on line 1620 in sentry_sdk/tracing_utils.py

See this annotation in the file changed.

@github-actions github-actions / warden: find-bugs

Dict rule with unrecognized keys in ignore_spans matches all spans

In `is_ignored_span()`, when a rule dict contains keys other than 'name' or 'attributes' (e.g., `{"foo": "bar"}`), both `name_matches` and `attributes_match` default to `True` and are never updated. This causes such rules to match every span, which is almost certainly not the user's intent. While TypedDict provides static type checking, Python doesn't enforce these constraints at runtime.