Skip to content

Commit 5f85345

Browse files
qing-antclaude
andcommitted
feat: surface api_error_status on ResultMessage
The CLI emits api_error_status: number | null on the final result message in stream-json output since v2.1.110. It is the HTTP status code (e.g. 429, 500, 529) of the failing API call when is_error=true and subtype="success", null otherwise. Previously the Python SDK silently dropped this field. Parallels the earlier errors field addition. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 3c08a3a commit 5f85345

3 files changed

Lines changed: 30 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
@@ -268,6 +268,7 @@ def parse_message(data: dict[str, Any]) -> Message | None:
268268
if deferred
269269
else None,
270270
errors=data.get("errors"),
271+
api_error_status=data.get("api_error_status"),
271272
uuid=data.get("uuid"),
272273
)
273274
except KeyError as e:

src/claude_agent_sdk/types.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1158,6 +1158,10 @@ class ResultMessage:
11581158
permission_denials: list[Any] | None = None
11591159
deferred_tool_use: DeferredToolUse | None = None
11601160
errors: list[str] | None = None
1161+
# HTTP status code (e.g. 429, 500, 529) of the failing API call when
1162+
# ``is_error`` is True and ``subtype`` is "success"; None otherwise.
1163+
# Emitted by the CLI since v2.1.110. Safe to log (no message content).
1164+
api_error_status: int | None = None
11611165
uuid: str | None = None
11621166

11631167

tests/test_message_parser.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,7 @@ def test_parse_result_message_optional_fields_absent(self):
918918
assert message.permission_denials is None
919919
assert message.deferred_tool_use is None
920920
assert message.errors is None
921+
assert message.api_error_status is None
921922
assert message.uuid is None
922923

923924
def test_parse_result_message_with_deferred_tool_use(self):
@@ -974,6 +975,30 @@ def test_parse_result_message_with_errors(self):
974975
assert message.subtype == "error_during_execution"
975976
assert message.uuid == "err-uuid-789"
976977

978+
def test_parse_result_message_with_api_error_status(self):
979+
"""ResultMessage surfaces api_error_status for failed API calls.
980+
981+
The CLI (v2.1.110+) emits api_error_status: number | null on the final
982+
result message — the HTTP status of the failing API call when
983+
is_error=True and subtype="success". This is the only safe-to-log
984+
signal for classifying API failures (e.g. 429 vs 529).
985+
"""
986+
data = {
987+
"type": "result",
988+
"subtype": "success",
989+
"duration_ms": 2000,
990+
"duration_api_ms": 1500,
991+
"is_error": True,
992+
"num_turns": 1,
993+
"session_id": "session_overload",
994+
"api_error_status": 529,
995+
}
996+
message = parse_message(data)
997+
assert isinstance(message, ResultMessage)
998+
assert message.api_error_status == 529
999+
assert message.is_error is True
1000+
assert message.subtype == "success"
1001+
9771002
def test_parse_result_message_success_no_errors(self):
9781003
"""Test that a successful result message has no errors field."""
9791004
data = {

0 commit comments

Comments
 (0)