Skip to content

Commit 94eaf75

Browse files
Fix: Properly populate AssistantMessage error field (#506)
## Summary Fixes #505 - Fix `AssistantMessage.error` field not being populated due to incorrect data path - The `error` field is at the top level of the response (`data["error"]`), not nested inside `data["message"]["error"]` - Add test coverage for error scenarios (authentication_failed, unknown, rate_limit) ## Test plan - [x] Added unit tests for assistant messages without errors - [x] Run `python -m pytest tests/test_message_parser.py` to verify all tests pass 🤖 Generated with [Claude Code](https://claude.com/claude-code)
1 parent b82f9b9 commit 94eaf75

2 files changed

Lines changed: 75 additions & 1 deletion

File tree

src/claude_agent_sdk/_internal/message_parser.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ def parse_message(data: dict[str, Any]) -> Message:
126126
content=content_blocks,
127127
model=data["message"]["model"],
128128
parent_tool_use_id=data.get("parent_tool_use_id"),
129-
error=data["message"].get("error"),
129+
error=data.get("error"),
130130
)
131131
except KeyError as e:
132132
raise MessageParseError(

tests/test_message_parser.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,3 +363,77 @@ def test_message_parse_error_contains_data(self):
363363
with pytest.raises(MessageParseError) as exc_info:
364364
parse_message(data)
365365
assert exc_info.value.data == data
366+
367+
def test_parse_assistant_message_without_error(self):
368+
"""Test that assistant message without error has error=None."""
369+
data = {
370+
"type": "assistant",
371+
"message": {
372+
"content": [{"type": "text", "text": "Hello"}],
373+
"model": "claude-opus-4-5-20251101",
374+
},
375+
}
376+
message = parse_message(data)
377+
assert isinstance(message, AssistantMessage)
378+
assert message.error is None
379+
380+
def test_parse_assistant_message_with_authentication_error(self):
381+
"""Test parsing assistant message with authentication_failed error.
382+
383+
The error field is at the top level of the data, not inside message.
384+
This matches the actual CLI output format.
385+
"""
386+
data = {
387+
"type": "assistant",
388+
"message": {
389+
"content": [
390+
{"type": "text", "text": "Invalid API key · Fix external API key"}
391+
],
392+
"model": "<synthetic>",
393+
},
394+
"session_id": "test-session",
395+
"error": "authentication_failed",
396+
}
397+
message = parse_message(data)
398+
assert isinstance(message, AssistantMessage)
399+
assert message.error == "authentication_failed"
400+
assert len(message.content) == 1
401+
assert isinstance(message.content[0], TextBlock)
402+
403+
def test_parse_assistant_message_with_unknown_error(self):
404+
"""Test parsing assistant message with unknown error (e.g., 404, 500).
405+
406+
When the CLI encounters API errors like model not found or server errors,
407+
it sets error to 'unknown' and includes the error details in the text content.
408+
"""
409+
data = {
410+
"type": "assistant",
411+
"message": {
412+
"content": [
413+
{
414+
"type": "text",
415+
"text": 'API Error: 500 {"type":"error","error":{"type":"api_error","message":"Internal server error"}}',
416+
}
417+
],
418+
"model": "<synthetic>",
419+
},
420+
"session_id": "test-session",
421+
"error": "unknown",
422+
}
423+
message = parse_message(data)
424+
assert isinstance(message, AssistantMessage)
425+
assert message.error == "unknown"
426+
427+
def test_parse_assistant_message_with_rate_limit_error(self):
428+
"""Test parsing assistant message with rate_limit error."""
429+
data = {
430+
"type": "assistant",
431+
"message": {
432+
"content": [{"type": "text", "text": "Rate limit exceeded"}],
433+
"model": "<synthetic>",
434+
},
435+
"error": "rate_limit",
436+
}
437+
message = parse_message(data)
438+
assert isinstance(message, AssistantMessage)
439+
assert message.error == "rate_limit"

0 commit comments

Comments
 (0)