fix: raise MessageParseError for non-dict message fields in parse_message#1090
Open
Otis0408 wants to merge 1 commit into
Open
fix: raise MessageParseError for non-dict message fields in parse_message#1090Otis0408 wants to merge 1 commit into
Otis0408 wants to merge 1 commit into
Conversation
Otis0408
force-pushed
the
fix/parse-message-typeerror-non-dict
branch
from
July 9, 2026 05:50
2ca055c to
89f20c0
Compare
…sage parse_message() documents `Raises: MessageParseError`, and existing tests (test_parse_assistant_string_content_raises, test_non_dict_content_block_raises_documented_error) already enforce that a malformed `content` value surfaces as MessageParseError, "never a raw TypeError". That guard only covered one nesting level, though: when the enclosing `message` (user/assistant), `rate_limit_info`, or `deferred_tool_use` object is present but is not a dict, the nested subscript (e.g. data["message"]["content"], info["status"], deferred["id"]) raised a raw TypeError that escaped the `except KeyError`-only handlers and reached the caller, bypassing the documented contract. Add isinstance guards mirroring the existing `isinstance(raw_content, list)` check so a non-dict `message` / `rate_limit_info` / `deferred_tool_use` raises a descriptive MessageParseError instead of a raw TypeError. Adds regression tests covering all four parse branches.
Otis0408
force-pushed
the
fix/parse-message-typeerror-non-dict
branch
from
July 9, 2026 07:47
89f20c0 to
726119b
Compare
tapheret2
reviewed
Jul 10, 2026
tapheret2
left a comment
There was a problem hiding this comment.
Review: fix: raise MessageParseError for non-dict message fields in parse_message
Files: src/claude_agent_sdk/_internal/message_parser.py, tests/test_message_parser.py
Size: +71 / -8
Notes
- Tests/fixtures included — good for merge confidence.
Nits
- Any user-facing change that should be mentioned in the PR description?
- Need a changelog/release note for the next tag?
Thanks @Otis0408 — independent review pass on the diff.
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
parse_message()documentsRaises: MessageParseError: If parsing fails, and the maintainers already enforce this one level down:test_parse_assistant_string_content_raisesandtest_non_dict_content_block_raises_documented_errorassert that a malformedcontentvalue surfaces asMessageParseError, "never a raw TypeError".That guard stops at
content. When the enclosing object is present but is not adict, the nested subscript raises a rawTypeErrorthat escapes theexcept KeyError-only handlers and reaches the caller, violating the documented contract:main{"type": "user", "message": "hi"}TypeError: string indices must be integers{"type": "assistant", "message": None}TypeError: 'NoneType' object is not subscriptable{"type": "rate_limit_event", "rate_limit_info": "x", ...}TypeError(info["status"]){"type": "result", ..., "deferred_tool_use": "oops"}TypeError(deferred["id"])Because neither public call site wraps
parse_message(ClaudeSDKClient.receive_messages()/receive_response()and thequery()path), these reach SDK consumers as a rawTypeError, so callers thatexcept MessageParseError(or the exported baseClaudeSDKError) miss them.Fix
Add
isinstanceguards mirroring the existingisinstance(raw_content, list)check, so a non-dictmessage/rate_limit_info/deferred_tool_useraises a descriptiveMessageParseErrorinstead of a rawTypeError.except KeyErrorhandlers and their existing messages (asserted by tests) are untouched, so missing-field cases still report "Missing required field ...".tryblocks; sinceMessageParseErroris not aKeyErrorit propagates as documented (same as the currentcontentguard).Tests
Adds regression tests covering all four branches, parametrized over
str/list/None/intmessage values. Verified they fail onmainwith the rawTypeErrorand pass with the fix.