Fix: prevent MessageParseError from killing SDK message stream#81
Merged
RichardAtCT merged 2 commits intomainfrom Feb 21, 2026
Merged
Fix: prevent MessageParseError from killing SDK message stream#81RichardAtCT merged 2 commits intomainfrom
RichardAtCT merged 2 commits intomainfrom
Conversation
…ge has empty session_id When Claude CLI returns an empty session_id in the ResultMessage, sessions become non-resumable. This adds a fallback that checks StreamEvent messages (which also carry session_id) to recover the session ID and enable session resumption. https://claude.ai/code/session_01WAbujHDo1pYht6sEZXinv4
The SDK's receive_messages() async generator was being terminated when encountering unknown message types (e.g. rate_limit_event). In Python, when an exception propagates out of an async generator, the generator dies permanently — causing all subsequent messages to be lost, including the ResultMessage that carries session_id and cost data. Fix: iterate over client._query.receive_messages() directly (raw dicts) and call parse_message() ourselves with error handling inside the loop. Unknown message types are now skipped without killing the generator. Also adds the StreamEvent session_id fallback (original PR intent) and comprehensive tests for both the fallback logic and parse error recovery. Before: session_id="", cost=0.0, sessions not resumable After: session_id properly extracted, cost tracked, sessions resumable
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
rate_limit_event) causedMessageParseErrorto propagate through and permanently terminate thereceive_messages()async generatorResultMessage— resulting in emptysession_id, zerocost, and non-resumable sessionssession_idfallback as defense-in-depthRoot Cause
The SDK's
receive_messages()generator callsparse_message(data)inside ayieldexpression. Whenparse_messageraisesMessageParseErrorfor unknown types likerate_limit_event, the exception propagates out of the async generator. In Python, this permanently terminates the generator — even if the caller catches the exception and tries to continue iterating.The previous code caught
MessageParseErrorfrom the caller side and calledcontinue, but this couldn't save the already-dead generator. The next__anext__()call receivedStopAsyncIteration, ending the loop beforeResultMessagecould arrive.Fix
Instead of using
client.receive_response()(which wraps the fragile generator), iterate overclient._query.receive_messages()directly (yields raw dicts) and callparse_message()ourselves with error handling inside the loop. Parse errors for unknown message types are now caught before they can kill the generator.Before / After
session_id""(empty)db7aa2b6-4dfa-4cfb-b268-d678ec082a91cost0.00.11rate_limit_eventTest plan