Skip to content

fix(openai): finish streaming gen_ai span on early stream close - #7852

Closed
intertermux-code wants to merge 1 commit into
getsentry:masterfrom
intertermux-code:fix/7847-stream-span-close
Closed

intertermux-code wants to merge 1 commit into
getsentry:masterfrom
intertermux-code:fix/7847-stream-span-close

Conversation

@intertermux-code

Copy link
Copy Markdown
Contributor

Description

Streaming chat-completions and Responses API calls lost their gen_ai.chat / gen_ai.responses span whenever the stream wasn't read to the end: the four chunk iterators in sentry_sdk/integrations/openai.py only finished the span in the code after their for loop, so an early stream.close(), leaving a with block after one chunk, or a dropped connection skipped finalization and the transaction went out without the span.

This ports the approach the Anthropic integration already uses: the loop and post-loop bookkeeping are wrapped in try/finally so the span is always finished (marked errored when the connection drops), and Stream.close / AsyncStream.close are patched to finish the span on early close. The span is attached to the stream object and detached on finish, so it is finished exactly once no matter which path runs first.

Issues

Reminders

  • Tests added (test_streaming_chat_completion_span_finished_on_early_exit, parametrized over break-then-close and with-block early exit; both fail without the fix)
  • Linted with ruff check / ruff format, mypy clean on the changed module
  • Full OpenAI integration suite green (686 passed)

The four streaming chunk iterators only finished the gen_ai.chat /
gen_ai.responses span in the code after their iteration loop. Closing the
stream early, leaving a `with` block after one chunk, or losing the
connection skipped that code, so the span was never finished and got
dropped from the transaction.

Wrap the loop and post-loop bookkeeping in try/finally so the span is
always finished (errored on connection errors), and patch Stream.close /
AsyncStream.close to finish the span too, mirroring the Anthropic
integration. The span is attached to the stream and detached on finish so
it is finished exactly once no matter which path runs first.

Fixes getsentry#7847.
@intertermux-code
intertermux-code requested a review from a team as a code owner October 5, 2026 05:49

@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 d0064c6. Configure here.

return
with capture_internal_exceptions():
del stream._span # type: ignore[union-attr]
span.__exit__(*exc_info)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Leaked exceptions mark stream spans failed

Medium Severity

Finishing the stream span with sys.exc_info() records whatever exception is currently active, not what happened on the stream. GeneratorExit from an abandoned or broken-off iterator, and any exception the caller is already handling, both reach span.__exit__, so gen_ai.chat / gen_ai.responses spans are marked internal_error even when the stream succeeded or was closed on purpose.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d0064c6. Configure here.

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.

OpenAI integration: streamed gen_ai.chat/gen_ai.responses span is lost when the stream is closed early or errors

2 participants