From 20e2323d7ed2804e8440ffefd926625577670a18 Mon Sep 17 00:00:00 2001 From: archievi <13202986+archievi@users.noreply.github.com> Date: Mon, 15 Jun 2026 15:21:28 -0400 Subject: [PATCH] fix: surface result text when error result has empty errors[] When the CLI emits a result message with is_error=true but an empty errors[] list (e.g. a model_not_found 404 where the protocol-level subtype is "success"), the SDK constructed the exception message from the subtype, producing the contradictory "Claude Code returned an error result: success". The human-readable error in the result field was discarded. Prefer the joined errors[] list, then fall back to the result field, and only then to the subtype. Add a regression test covering the empty-errors / subtype=success case. Fixes #1031 --- src/claude_agent_sdk/_internal/query.py | 12 ++++++-- tests/test_query.py | 38 +++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/claude_agent_sdk/_internal/query.py b/src/claude_agent_sdk/_internal/query.py index 7a4f8a447..af97a5168 100644 --- a/src/claude_agent_sdk/_internal/query.py +++ b/src/claude_agent_sdk/_internal/query.py @@ -303,8 +303,16 @@ async def _read_messages(self) -> None: self._first_result_event.set() if message.get("is_error"): errors = message.get("errors") or [] - self._last_error_result_text = "; ".join(errors) or str( - message.get("subtype", "unknown error") + # Prefer the joined errors[] list, then the + # human-readable "result" field, before falling back to + # the subtype. For some API-level errors (e.g. + # model_not_found) errors[] is empty and subtype is + # "success", which would otherwise produce the + # contradictory "returned an error result: success". + self._last_error_result_text = ( + "; ".join(errors) + or message.get("result") + or str(message.get("subtype", "unknown error")) ) else: self._last_error_result_text = None diff --git a/tests/test_query.py b/tests/test_query.py index 3ae65093d..b946c3950 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -1059,6 +1059,44 @@ async def _test(): anyio.run(_test) + def test_process_error_empty_errors_uses_result_field_not_subtype(self): + """Regression for #1031: when errors[] is empty but the result carries + a human-readable "result" field (e.g. a model_not_found 404 where the + protocol-level subtype is "success"), surface the result text instead + of the misleading "Claude Code returned an error result: success".""" + + async def _test(): + transport = self._make_transport_then_raise( + messages=[ + self._error_result( + subtype="success", + errors=[], + result=( + "There's an issue with the selected model " + "(nonexistent-model). It may not exist or you may " + "not have access to it." + ), + api_error_status=404, + ) + ], + exc=ProcessError( + "Command failed with exit code 1", exit_code=1, stderr="" + ), + ) + q = Query(transport=transport, is_streaming_mode=True) + await q.start() + + with pytest.raises( + Exception, + match=r"Claude Code returned an error result: There's an issue " + r"with the selected model \(nonexistent-model\)\.", + ): + async for _ in q.receive_messages(): + pass + await q.close() + + anyio.run(_test) + def test_process_error_without_result_keeps_original_message(self): async def _test(): transport = self._make_transport_then_raise(