From a6cfb79e87846d0e80b876d176727e835c87ef8c Mon Sep 17 00:00:00 2001 From: dumko2001 Date: Fri, 12 Jun 2026 01:27:52 +0530 Subject: [PATCH] Prefer result field over subtype when building ProcessError replacement text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the CLI exits non-zero with is_error=True + errors=[] + a non-empty result string (the pattern for API-level failures: model_not_found, overload, etc.), the previous fallback landed on subtype — typically "success" for these cases — producing the confusing message "Claude Code returned an error result: success". The result field already holds the human-readable error text; check it before falling back to subtype. Priority is unchanged for the common cases: errors[] join wins when present, subtype is still the last resort for old CLI payloads with neither field. Fixes #1031 --- src/claude_agent_sdk/_internal/query.py | 6 ++-- tests/test_query.py | 45 +++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/src/claude_agent_sdk/_internal/query.py b/src/claude_agent_sdk/_internal/query.py index 7a4f8a447..d74aa96e6 100644 --- a/src/claude_agent_sdk/_internal/query.py +++ b/src/claude_agent_sdk/_internal/query.py @@ -303,8 +303,10 @@ 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") + 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..8c6e005c5 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -1009,9 +1009,50 @@ async def _test(): anyio.run(_test) + def test_process_error_uses_result_field_when_errors_empty(self): + """When errors[] is empty but result carries the message, use result. + + The CLI emits is_error=True + subtype="success" + errors=[] + a human- + readable result string for API-level failures such as model_not_found + (HTTP 404) or overload (HTTP 529). Without this fix the exception text + is 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 " + "(claude-nonexistent). 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: " + r"There's an issue with the selected model", + ): + async for _ in q.receive_messages(): + pass + await q.close() + + anyio.run(_test) + def test_process_error_after_error_result_falls_back_to_subtype(self): - """When the result has no errors[] (older CLI / minimal payload), the - improved message falls back to the subtype so it's still actionable.""" + """When neither errors[] nor result is present (older CLI / minimal + payload), the message falls back to subtype so it's still actionable.""" async def _test(): transport = self._make_transport_then_raise(