fix(tracing): Skip child span creation in streaming path when no current span (HTTP clients)#6811
fix(tracing): Skip child span creation in streaming path when no current span (HTTP clients)#6811sentrivana wants to merge 2 commits into
Conversation
…ent span (HTTP clients) When span streaming is enabled and there is no current span, HTTP client integrations should not create new root segments for child-span operations. This adds a guard check using `sentry_sdk.traces.get_current_span()` before creating spans in the streaming path. Affected integrations: httpx, httpx2, pyreqwest, boto3, stdlib. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| if sentry_sdk.traces.get_current_span() is None: | ||
| return real_send(self, request, **kwargs) |
There was a problem hiding this comment.
Early return skips trace propagation headers on outgoing requests in streaming mode
When streaming is enabled and there is no current span, returning real_send() early bypasses should_propagate_trace and header injection, so sentry-trace/baggage headers are no longer added to outgoing requests. This breaks distributed tracing continuity: an incoming/continued trace's propagation context would no longer be forwarded downstream when no span is active, whereas the non-streaming branch always propagates. The change intended only to avoid creating orphan root segments, but it also drops header propagation as a side effect. Consider still injecting propagation headers before returning early.
Evidence
- In
_install_httpx_client.send(httpx.py:62-64) the new guardif sentry_sdk.traces.get_current_span() is None: return real_send(...)returns before thewith start_spanblock, which is the only place in the streaming path that callsshould_propagate_traceand writessentry-trace/baggageheaders. Scope.iter_trace_propagation_headers(scope.py:698-716) falls back toget_active_propagation_context().iter_headers()when no span is present, so propagation headers are normally emitted even with no active span.- The non-streaming
elsebranch (httpx.py) always runsshould_propagate_trace+ header injection, so skipping it in streaming mode is an asymmetric behavioral regression. - The same pattern was added to the async client (httpx.py) and to httpx2/boto3/pyreqwest/stdlib, so the propagation gap applies to all patched HTTP clients.
Also found at 4 additional locations
sentry_sdk/integrations/httpx.py:176-178sentry_sdk/integrations/httpx.py:63-64sentry_sdk/integrations/httpx2.py:176-178sentry_sdk/integrations/stdlib.py:308-310
Identified by Warden code-review · FFE-WY6
| if sentry_sdk.traces.get_current_span() is None: | ||
| return await real_send(self, request, **kwargs) | ||
|
|
There was a problem hiding this comment.
Trace propagation headers dropped when no current span in streaming path
This early return bypasses the should_propagate_trace/iter_trace_propagation_headers block, so outgoing HTTP requests get no sentry-trace/baggage headers when span streaming is enabled and there is no current span, breaking distributed trace continuation.
Evidence
iter_trace_propagation_headersinscope.py(lines 703-716) yields headers fromget_active_propagation_context().iter_headers()even whenspan is None, so propagation does not require an active span.- In the async
send, the addedif sentry_sdk.traces.get_current_span() is None: return await real_send(...)runs before theif should_propagate_trace(client, str(request.url))header-injection block. - Result: no
sentry-trace/baggageheaders are added to the outgoing request in that branch, whereas the non-streamingelsebranch always injects them. - The same pattern appears in
stdlib.pyputrequest, where the guard returns before theshould_propagate_traceloop that callsself.putheader(key, value).
Also found at 5 additional locations
sentry_sdk/integrations/httpx2.py:63-64sentry_sdk/integrations/httpx.py:63-65sentry_sdk/integrations/httpx2.py:176-178sentry_sdk/integrations/stdlib.py:117-118sentry_sdk/integrations/stdlib.py:309-310
Identified by Warden find-bugs · YUR-XRB
…an guards httpx2: Wrap HTTP calls in parent span context so child spans are created. strawberry: Remove async/sync branching since both paths now produce 5 spans. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Codecov Results 📊✅ 90744 passed | ❌ 336 failed | ⏭️ 6297 skipped | Total: 97377 | Pass Rate: 93.19% | Execution Time: 306m 33s 📊 Comparison with Base Branch
➕ New Tests (336)View new tests
❌ Failed Tests
|
| File | Patch % | Lines |
|---|---|---|
| sentry_sdk/integrations/httpx2.py | 50.00% | |
| sentry_sdk/integrations/stdlib.py | 75.00% | |
| sentry_sdk/integrations/boto3.py | 50.00% | |
| sentry_sdk/integrations/httpx.py | 100.00% | |
| sentry_sdk/integrations/pyreqwest.py | 100.00% |
Coverage diff
@@ Coverage Diff @@
## main #PR +/-##
==========================================
- Coverage 89.80% 89.61% -0.19%
==========================================
Files 193 193 —
Lines 23898 23923 +25
Branches 8258 8286 +28
==========================================
+ Hits 21461 21438 -23
- Misses 2437 2485 +48
- Partials 1353 1359 +6Generated by Codecov Action
Summary
sentry_sdk.traces.get_current_span() is Noneguard before creating spans in the streaming pathTest plan
🤖 Generated with Claude Code