Skip to content

🪲 BUG-#62: Persist already-shown text when an SSE chunk is malformed (closes #62, #64) - #67

Merged
FernandoCelmer merged 4 commits into
masterfrom
feature/62
Aug 16, 2026
Merged

🪲 BUG-#62: Persist already-shown text when an SSE chunk is malformed (closes #62, #64)#67
FernandoCelmer merged 4 commits into
masterfrom
feature/62

Conversation

@FernandoCelmer

Copy link
Copy Markdown
Member

Description

  • pycodeloop/providers/generic.py_stream(): wraps json.loads(payload) in try/except json.JSONDecodeError, breaking out of the loop and returning whatever text/tool-calls were already accumulated (stop_reason="malformed_stream") instead of letting the exception discard everything the caller already streamed to the user. Also tracks whether the loop ended via an explicit terminal marker ([DONE] or a finish_reason chunk) — if the iterator just runs out without one (connection dropped mid-response), stop_reason is now "connection_lost" instead of silently defaulting to "stop", which used to make a truncated response indistinguishable from a clean completion.
  • tests/providers/test_generic.py — Adds test_streaming_keeps_already_shown_text_on_malformed_chunk and test_streaming_flags_a_connection_dropped_mid_response. Verified both fail without the fix (the first with an unhandled JSONDecodeError, the second with stop_reason == "stop" instead of "connection_lost") and pass with it.

Motivation and Context

Two related integrity bugs in the same streaming path:

  • Malformed SSE chunk drops already-streamed text from history #62 — A truncated/corrupt data: line mid-stream raised unhandled. By that point on_delta(...) had already fired for every prior chunk — those deltas already reached the client and rendered on screen — but the exception propagated up, the turn failed, and session.add_assistant(...) was never called, so the text the user already saw never entered the persisted session.
  • Silently-dropped stream connection recorded as a clean stop #64 — If the connection dropped without an explicit finish_reason/[DONE] marker, the for loop just ended and stop_reason silently defaulted to "stop", making a truncated response look like the model finished on its own — future turns build on a "complete" message that's actually cut off mid-thought.

Closes #62. Closes #64.

Types of changes

  • Bug fix (change that fixes an issue)
  • New feature
  • Documentation

Checklist

  • I have performed a self-review of my own code
  • I have added tests that prove my fix is effective
  • I have updated the CHANGELOG
  • I have updated the documentation accordingly

@FernandoCelmer FernandoCelmer left a comment

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.

🔍 Code Review

Code issues found: 2

# Severity Comment
1 [Blocking] stop_reason is overwritten when a malformed chunk follows a clean finish_reason
2 [Suggestion] Missing test for the finish_reason → malformed sequence

Comment thread pycodeloop/providers/generic.py
Comment thread tests/providers/test_generic.py
@FernandoCelmer FernandoCelmer added the bug Something isn't working label Aug 16, 2026
@FernandoCelmer
FernandoCelmer merged commit 532b281 into master Aug 16, 2026
8 checks passed
@FernandoCelmer
FernandoCelmer deleted the feature/62 branch August 16, 2026 03:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Silently-dropped stream connection recorded as a clean stop Malformed SSE chunk drops already-streamed text from history

1 participant