diff --git a/src/claude_agent_sdk/_internal/message_parser.py b/src/claude_agent_sdk/_internal/message_parser.py index 931cc2a63..d1e18d8d4 100644 --- a/src/claude_agent_sdk/_internal/message_parser.py +++ b/src/claude_agent_sdk/_internal/message_parser.py @@ -95,6 +95,12 @@ def parse_message(data: dict[str, Any]) -> Message | None: match message_type: case "user": try: + if not isinstance(data["message"], dict): + raise MessageParseError( + f"Invalid message field (expected dict, got " + f"{type(data['message']).__name__})", + data, + ) parent_tool_use_id = data.get("parent_tool_use_id") tool_use_result = data.get("tool_use_result") uuid = data.get("uuid") @@ -150,6 +156,12 @@ def parse_message(data: dict[str, Any]) -> Message | None: case "assistant": try: + if not isinstance(data["message"], dict): + raise MessageParseError( + f"Invalid message field (expected dict, got " + f"{type(data['message']).__name__})", + data, + ) raw_content = data["message"]["content"] if not isinstance(raw_content, list): raise MessageParseError( diff --git a/tests/test_message_parser.py b/tests/test_message_parser.py index e55fd1556..9ee093326 100644 --- a/tests/test_message_parser.py +++ b/tests/test_message_parser.py @@ -1015,6 +1015,15 @@ def test_non_dict_content_block_raises_documented_error(self, role: str) -> None with pytest.raises(MessageParseError): parse_message({"type": role, "message": message}) + @pytest.mark.parametrize("role", ["assistant", "user"]) + @pytest.mark.parametrize("bad_message", ["hi", ["hi"], 5, None]) + def test_non_dict_message_field_raises_documented_error( + self, role: str, bad_message: object + ) -> None: + """A non-dict message field raises MessageParseError, never a raw TypeError.""" + with pytest.raises(MessageParseError): + parse_message({"type": role, "message": bad_message}) + def test_parse_system_message_missing_fields(self): """Test that system message with missing fields raises MessageParseError.""" with pytest.raises(MessageParseError) as exc_info: