Skip to content

fix(boto3): Finish StreamingBody span correctly - #7540

Merged
pabloDeputter merged 5 commits into
pablo/refactor-boto3-integrationfrom
pablo/harden-boto3-streaming-body
Sep 30, 2026
Merged

pabloDeputter merged 5 commits into
pablo/refactor-boto3-integrationfrom
pablo/harden-boto3-streaming-body

Conversation

@pabloDeputter

@pabloDeputter pabloDeputter commented Sep 18, 2026 •

Copy link
Copy Markdown
Member

Description

Finish boto streaming spans when a StreamingBody is consumed, closed or fails. Previously, streaming spans where only finished when a read returned no data or StreamingBody.close() was called.

@pabloDeputter
pabloDeputter requested a review from a team as a code owner September 18, 2026 15:06
@pabloDeputter
pabloDeputter marked this pull request as draft September 18, 2026 15:07
@pabloDeputter
pabloDeputter added this pull request to stack #7541 September 18, 2026 15:08
@pabloDeputter pabloDeputter changed the title fix(boto3): harden StreamingBody span finalization fix(boto3): Finish StreamingBody span correctly Sep 18, 2026
@pabloDeputter
pabloDeputter marked this pull request as ready for review September 18, 2026 15:14
@github-actions

github-actions Bot commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Codecov Results 📊

✅ 132259 passed | ⏭️ 7214 skipped | Total: 139473 | Pass Rate: 94.83% | Execution Time: 427m 1s

📊 Comparison with Base Branch

Metric Change
Total Tests 📈 +2889
Passed Tests 📈 +2932
Failed Tests 📉 -60
Skipped Tests 📈 +17

➖ Removed Tests (60)

View removed tests
  • test_input_attributes_nonstreaming_chat[False-True-False-messages1-expected_system_instructions1-expected_input_messages1]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat[False-True-False-messages2-expected_system_instructions2-expected_input_messages2]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat[False-True-False-messages3-expected_system_instructions3-expected_input_messages3]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat[False-False-True-messages0-expected_system_instructions0-expected_input_messages0]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat[False-False-True-messages1-expected_system_instructions1-expected_input_messages1]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat[False-False-True-messages2-expected_system_instructions2-expected_input_messages2]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat[False-False-True-messages3-expected_system_instructions3-expected_input_messages3]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat[False-False-False-messages0-expected_system_instructions0-expected_input_messages0]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat[False-False-False-messages1-expected_system_instructions1-expected_input_messages1]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat[False-False-False-messages2-expected_system_instructions2-expected_input_messages2]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat[False-False-False-messages3-expected_system_instructions3-expected_input_messages3]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[True-True-True-messages0-expected_system_instructions0-expected_input_messages0]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[True-True-True-messages1-expected_system_instructions1-expected_input_messages1]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[True-True-True-messages2-expected_system_instructions2-expected_input_messages2]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[True-True-True-messages3-expected_system_instructions3-expected_input_messages3]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[True-True-False-messages0-expected_system_instructions0-expected_input_messages0]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[True-True-False-messages1-expected_system_instructions1-expected_input_messages1]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[True-True-False-messages2-expected_system_instructions2-expected_input_messages2]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[True-True-False-messages3-expected_system_instructions3-expected_input_messages3]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[True-False-True-messages0-expected_system_instructions0-expected_input_messages0]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[True-False-True-messages1-expected_system_instructions1-expected_input_messages1]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[True-False-True-messages2-expected_system_instructions2-expected_input_messages2]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[True-False-True-messages3-expected_system_instructions3-expected_input_messages3]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[True-False-False-messages0-expected_system_instructions0-expected_input_messages0]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[True-False-False-messages1-expected_system_instructions1-expected_input_messages1]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[True-False-False-messages2-expected_system_instructions2-expected_input_messages2]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[True-False-False-messages3-expected_system_instructions3-expected_input_messages3]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[False-True-True-messages0-expected_system_instructions0-expected_input_messages0]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[False-True-True-messages1-expected_system_instructions1-expected_input_messages1]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[False-True-True-messages2-expected_system_instructions2-expected_input_messages2]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[False-True-True-messages3-expected_system_instructions3-expected_input_messages3]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[False-True-False-messages0-expected_system_instructions0-expected_input_messages0]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[False-True-False-messages1-expected_system_instructions1-expected_input_messages1]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[False-True-False-messages2-expected_system_instructions2-expected_input_messages2]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[False-True-False-messages3-expected_system_instructions3-expected_input_messages3]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[False-False-True-messages0-expected_system_instructions0-expected_input_messages0]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[False-False-True-messages1-expected_system_instructions1-expected_input_messages1]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[False-False-True-messages2-expected_system_instructions2-expected_input_messages2]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[False-False-True-messages3-expected_system_instructions3-expected_input_messages3]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[False-False-False-messages0-expected_system_instructions0-expected_input_messages0]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[False-False-False-messages1-expected_system_instructions1-expected_input_messages1]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[False-False-False-messages2-expected_system_instructions2-expected_input_messages2]
    • File: tests.integrations.mistral.test_mistral
  • test_input_attributes_nonstreaming_chat_async[False-False-False-messages3-expected_system_instructions3-expected_input_messages3]
    • File: tests.integrations.mistral.test_mistral
  • test_output_attributes_nonstreaming_chat[True-True-True]
    • File: tests.integrations.mistral.test_mistral
  • test_output_attributes_nonstreaming_chat[True-True-False]
    • File: tests.integrations.mistral.test_mistral
  • test_output_attributes_nonstreaming_chat[True-False-True]
    • File: tests.integrations.mistral.test_mistral
  • test_output_attributes_nonstreaming_chat[True-False-False]
    • File: tests.integrations.mistral.test_mistral
  • test_output_attributes_nonstreaming_chat[False-True-True]
    • File: tests.integrations.mistral.test_mistral
  • test_output_attributes_nonstreaming_chat[False-True-False]
    • File: tests.integrations.mistral.test_mistral
  • test_output_attributes_nonstreaming_chat[False-False-True]
    • File: tests.integrations.mistral.test_mistral
  • test_output_attributes_nonstreaming_chat[False-False-False]
    • File: tests.integrations.mistral.test_mistral
  • test_output_attributes_nonstreaming_chat_async[True-True-True]
    • File: tests.integrations.mistral.test_mistral
  • test_output_attributes_nonstreaming_chat_async[True-True-False]
    • File: tests.integrations.mistral.test_mistral
  • test_output_attributes_nonstreaming_chat_async[True-False-True]
    • File: tests.integrations.mistral.test_mistral
  • test_output_attributes_nonstreaming_chat_async[True-False-False]
    • File: tests.integrations.mistral.test_mistral
  • test_output_attributes_nonstreaming_chat_async[False-True-True]
    • File: tests.integrations.mistral.test_mistral
  • test_output_attributes_nonstreaming_chat_async[False-True-False]
    • File: tests.integrations.mistral.test_mistral
  • test_output_attributes_nonstreaming_chat_async[False-False-True]
    • File: tests.integrations.mistral.test_mistral
  • test_output_attributes_nonstreaming_chat_async[False-False-False]
    • File: tests.integrations.mistral.test_mistral
  • test_cache_spans_templatetag[True]
    • File: tests.integrations.django.test_cache_module

All tests are passing successfully.

✅ Patch coverage is 86.76%. Project has 2554 uncovered lines.
✅ Project coverage is 90.3%. Comparing base (b21b888) to head (364f547).

Files with missing lines (1)
File Patch % Lines
sentry_sdk/integrations/boto3/_instrumentation.py 86.76% ⚠️ 9 Missing
Coverage diff
@@            Coverage Diff             @@
##        master       #PR       +/-##
==========================================
+ Coverage    90.30%    90.30%        —%
==========================================
  Files          195       198        +3
  Lines        26261     26322       +61
  Branches      9798      9804        +6
==========================================
+ Hits         23714     23768       +54
- Misses        2547      2554        +7
- Partials      1485      1484        -1

Generated by Codecov Action

Comment on lines 208 to +213
orig_read = body.read
orig_close = body.close
raw_stream = body._raw_stream # type: ignore[attr-defined]
orig_raw_close = raw_stream.close
finished = False

@sentry-warden sentry-warden Bot Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Streaming span leaks if setup fails before guarded try

Move _raw_stream access (and the other pre-try setup) into the existing try/except so finish() still runs if instrumentation setup raises after the child span is created.

Evidence
  • _instrument_streaming_body() creates streaming_span before setup completes.
  • body._raw_stream / raw_stream.close run outside the later try that calls finish() on failure.
  • _sentry_after_call() invokes _instrument_streaming_body() under capture_internal_exceptions(), so a setup exception is swallowed and the child span is never finished.

Identified by Warden · code-review, find-bugs · HDS-MQ3

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll check this out in the next PR, cause I don't want to have merge issues

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix attempt detected (commit ff199b0)

The commit clearly attempts to harden streaming span finalization, but _raw_stream access and other setup still occur before the guarded try, so an exception there can still leak the child span.

The original issue appears unresolved. Please review and try again.

Evaluated by Warden

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix attempt detected (commit 11ea5b8)

The change clearly attempts to harden streaming span finalization, but body._raw_stream and related setup still execute before the try/except that calls finish_span, so a setup exception can still leak the child span.

The original issue appears unresolved. Please review and try again.

Evaluated by Warden

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix attempt detected (commit 65dfa57)

The change clearly attempts to harden streaming-span finalization, but accesses to body._raw_stream and raw_stream.close still occur before the try/except, so setup failures there can still leak the child span.

The original issue appears unresolved. Please review and try again.

Evaluated by Warden

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix attempt detected (commit 4892911)

The change adds guarded cleanup for instrumentation assignments, but _raw_stream access and other setup remain before the try, so an exception there still leaks the child span.

The original issue appears unresolved. Please review and try again.

Evaluated by Warden

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix attempt detected (commit d7a3959)

The change clearly attempts to finalize the streaming span on setup failures, but accesses to body.read, body.close, body._raw_stream, and raw_stream.close remain before the guarded try, so an exception there still leaks the span.

The original issue appears unresolved. Please review and try again.

Evaluated by Warden

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix attempt detected (commit 09a038e)

The change clearly targets streaming-span finalization and adds cleanup for failures during wrapper assignment, but accessing body.read, body.close, and body._raw_stream.close still occurs before the guarded try and before finish_span is defined, so a setup exception can still leak the child span.

The original issue appears unresolved. Please review and try again.

Evaluated by Warden

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix attempt detected (commit 364f547)

The change clearly hardens streaming-span finalization, but _raw_stream access and other setup still occur before the guarded try, so a setup exception can leave the child span unfinished.

The original issue appears unresolved. Please review and try again.

Evaluated by Warden

span: "Union[Span, StreamedSpan]", parsed: "Dict[str, Any]"
) -> bool:
if isinstance(span, NoOpStreamedSpan):
return False

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why return a boolean here when we're not using the result?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I probably messed up when splitting the large PR in smaller ones; in the next PR of the stack, we'll use this return value to check whether there was 1. a StreamingBody as response and 2. it was instrumented; since we only want to delay closing the boto span if both conditions are met.

I'll document this better in the next PR.

Comment thread sentry_sdk/integrations/boto3/_instrumentation.py
Comment on lines +233 to +235
ret = orig_read(*args, **kwargs)
if ret:
return ret

if isinstance(streaming_span, StreamedSpan):
streaming_span.end()
else:
streaming_span.finish()
with capture_internal_exceptions():
amount = args[0] if args else kwargs.get("amt")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple of variable name suggestions to improve readability:

ret => read_return_value
amount => amount_of_bytes_requested

orig_raw_close = raw_stream.close
finished = False

def finish(error: "Optional[BaseException]" = None) -> None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not specific to your changes and more of a general note on why I'm suggesting this (and other) renames:

We have more than a few vague names within the SDK - both within the "working code" and our tests. This makes it difficult to understand what exactly is being invoked when calling a method, what data is represented by a variable, or what behaviour we're looking to test.

I'd like us to try and move a bit more in the direction of being more specific so that someone reading this in the future doesn't have to jump to function definitions or ask a clanker what pieces of data mean or what's being tested (at least as often as we may have to now).

Suggested change
def finish(error: "Optional[BaseException]" = None) -> None:
def finish_span(error: "Optional[BaseException]" = None) -> None:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good to know, I'll try to keep this in mind in the future.

sentry_init(
traces_sample_rate=1.0,
integrations=[Boto3Integration()],
trace_lifecycle="stream" if span_streaming else "static",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When these changes land, we're going to have to make sure that we remove the branching on span_streaming in the major/3.0 branch 😅

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yupp, there are quite a lot of these cases 😬

@pabloDeputter
pabloDeputter force-pushed the pablo/harden-boto3-streaming-body branch from dedab66 to 5383b90 Compare September 21, 2026 08:41
@pabloDeputter
pabloDeputter force-pushed the pablo/harden-boto3-streaming-body branch from 66d4665 to 54c499e Compare September 21, 2026 15:39

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 54c499e. Configure here.

Comment thread sentry_sdk/integrations/boto3/_instrumentation.py
@pabloDeputter
pabloDeputter force-pushed the pablo/harden-boto3-streaming-body branch from ff199b0 to 05d5050 Compare September 22, 2026 08:52
@pabloDeputter
pabloDeputter force-pushed the pablo/harden-boto3-streaming-body branch 2 times, most recently from 11ea5b8 to 65dfa57 Compare September 22, 2026 14:53
@pabloDeputter
pabloDeputter removed this pull request from stack #7541 September 22, 2026 14:57
@pabloDeputter
pabloDeputter added this pull request to stack #7618 September 22, 2026 14:57
@pabloDeputter
pabloDeputter force-pushed the pablo/harden-boto3-streaming-body branch from 65dfa57 to 4892911 Compare September 29, 2026 08:34
@pabloDeputter
pabloDeputter force-pushed the pablo/harden-boto3-streaming-body branch from 4892911 to d7a3959 Compare September 30, 2026 12:04
@pabloDeputter
pabloDeputter removed this pull request from stack #7618 September 30, 2026 12:05
@pabloDeputter
pabloDeputter added this pull request to stack #7792 September 30, 2026 12:05
@pabloDeputter
pabloDeputter force-pushed the pablo/harden-boto3-streaming-body branch from d7a3959 to 09a038e Compare September 30, 2026 13:31
@pabloDeputter
pabloDeputter force-pushed the pablo/harden-boto3-streaming-body branch from 09a038e to 364f547 Compare September 30, 2026 14:05
@pabloDeputter
pabloDeputter merged commit 500f10e into master Sep 30, 2026
259 of 473 checks passed
@pabloDeputter
pabloDeputter deleted the pablo/harden-boto3-streaming-body branch September 30, 2026 15:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants