Skip to content

Commit f9fc8e0

Browse files
authored
fix: add missing errors field to ResultMessage (#749)
The TypeScript SDK's `SDKResultErrorSchema` defines `errors: z.array(z.string())` on error result messages (subtypes: `error_during_execution`, `error_max_turns`, `error_max_budget_usd`, `error_max_structured_output_retries`). This array contains diagnostic strings explaining why the CLI exited with an error. The Python SDK's `ResultMessage` dataclass was missing this field, so when the CLI emitted error results with an `errors` array, the data was silently dropped. SDK users had no way to programmatically access error diagnostics from non-zero CLI exits. ## Changes - Add `errors: list[str] | None = None` to the `ResultMessage` dataclass in `types.py` - Extract the `errors` field from JSON in the message parser (`message_parser.py`) - Add regression tests verifying: - Error result messages with `errors` array are correctly parsed - Success result messages have `errors` defaulting to `None` - The `errors` field defaults to `None` when absent (backward compatible) ## Testing All 45 message parser tests pass, including 2 new tests for this change.
1 parent dec0ecb commit f9fc8e0

3 files changed

Lines changed: 51 additions & 0 deletions

File tree

src/claude_agent_sdk/_internal/message_parser.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ def parse_message(data: dict[str, Any]) -> Message | None:
209209
structured_output=data.get("structured_output"),
210210
model_usage=data.get("modelUsage"),
211211
permission_denials=data.get("permission_denials"),
212+
errors=data.get("errors"),
212213
uuid=data.get("uuid"),
213214
)
214215
except KeyError as e:

src/claude_agent_sdk/types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,7 @@ class ResultMessage:
915915
structured_output: Any = None
916916
model_usage: dict[str, Any] | None = None
917917
permission_denials: list[Any] | None = None
918+
errors: list[str] | None = None
918919
uuid: str | None = None
919920

920921

tests/test_message_parser.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -825,4 +825,53 @@ def test_parse_result_message_optional_fields_absent(self):
825825
assert isinstance(message, ResultMessage)
826826
assert message.model_usage is None
827827
assert message.permission_denials is None
828+
assert message.errors is None
828829
assert message.uuid is None
830+
831+
def test_parse_result_message_with_errors(self):
832+
"""Test that ResultMessage preserves the errors field from error results.
833+
834+
The CLI emits errors: string[] on error result messages (subtypes like
835+
error_during_execution, error_max_turns, etc.). Without this field,
836+
SDK users cannot diagnose why a non-zero exit occurred.
837+
"""
838+
data = {
839+
"type": "result",
840+
"subtype": "error_during_execution",
841+
"duration_ms": 5000,
842+
"duration_api_ms": 3000,
843+
"is_error": True,
844+
"num_turns": 3,
845+
"session_id": "session_456",
846+
"errors": [
847+
"Tool execution failed: permission denied",
848+
"Unable to write to /etc/hosts",
849+
],
850+
"uuid": "err-uuid-789",
851+
}
852+
message = parse_message(data)
853+
assert isinstance(message, ResultMessage)
854+
assert message.errors == [
855+
"Tool execution failed: permission denied",
856+
"Unable to write to /etc/hosts",
857+
]
858+
assert message.is_error is True
859+
assert message.subtype == "error_during_execution"
860+
assert message.uuid == "err-uuid-789"
861+
862+
def test_parse_result_message_success_no_errors(self):
863+
"""Test that a successful result message has no errors field."""
864+
data = {
865+
"type": "result",
866+
"subtype": "success",
867+
"duration_ms": 1000,
868+
"duration_api_ms": 500,
869+
"is_error": False,
870+
"num_turns": 1,
871+
"session_id": "session_789",
872+
"result": "Task completed successfully",
873+
}
874+
message = parse_message(data)
875+
assert isinstance(message, ResultMessage)
876+
assert message.errors is None
877+
assert message.result == "Task completed successfully"

0 commit comments

Comments
 (0)