Keep the real error text on is_error results, and raise a typed error - #1181
Open
adhavan18 wants to merge 1 commit into
Open
Keep the real error text on is_error results, and raise a typed error#1181adhavan18 wants to merge 1 commit into
adhavan18 wants to merge 1 commit into
Conversation
Fixes anthropics#1146. When the loop completes but the final turn was an API error, the CLI sends `{"subtype": "success", "is_error": true, "errors": [], "result": "API Error: ..."}` and exits non-zero. `errors` is empty, so `"; ".join(errors)` was falsy and the text fell back to `subtype`, producing: Exception: Claude Code returned an error result: success which contradicts itself and drops the prose the CLI already provided. It now prefers `result`, and only uses `subtype` when it actually names an error: ResultError: Claude Code returned an error result: API Error: Stream idle timeout - no chunks received The second half of the issue was the bare `Exception`, which callers cannot catch as an SDK error. Terminal error results now raise `ResultError`, a new `ClaudeSDKError` subclass carrying `subtype`, `errors` and `exit_code`, so the structured fields survive instead of only the formatted string. The frame that crosses the internal queue carries those fields for it. Non-result errors still raise a plain `Exception`, so nothing else changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1146.
The bug
When the agent loop completes but the final turn was an API error, the CLI emits a terminal result with an empty
errorsarray andsubtype: "success", putting the real prose inresult:{"type": "result", "subtype": "success", "is_error": true, "errors": [], "result": "API Error: Stream idle timeout - no chunks received"}"; ".join([])is falsy, so the text fell through tosubtype. Reproduced on currentmain:Self-contradictory, and the actual error was discarded.
The change
1. Prefer the
resulttext. The order is nowerrors→result→subtype, andsubtypeis only used when it actually names an error:The
startswith("error")guard is the second half of the issue's suggestion. Without it,subtype: "success"would still come through as the error text wheneverresultis absent, which is the same contradiction in a rarer shape. There is a test for that case.2. Raise a typed error. Terminal error results now raise
ResultError(ClaudeSDKError)instead of a bareException, so callers can catch it. It carriessubtype,errorsandexit_code, so the structured fields survive rather than only the formatted string:The internal error frame carries those fields across the queue so the raise site can populate them. Non-result errors still raise a plain
Exception, so nothing else changes.Why a new error type rather than
ProcessErrorProcessErrormeans "the process failed", and its message already appends(exit code: N). Here the process did exactly what it was told: it reported a structured result and exited non-zero on purpose. Reusing it would conflate "the CLI crashed" with "the CLI reported an error result", which are different things for a caller deciding whether to retry.ResultErrorstill exposesexit_code, so nothing is lost.I am happy to switch to
ProcessErrorif you would rather not add a public name.Testing
tests/test_error_result_text.pydrives a stub transport through the realQueryread loop:subtypeResultError, and is catchable asClaudeSDKError, withsubtype/errors/exit_codepopulatederrorslist still winserror_*subtype is still used when there is no proseruff check,ruff formatandmypyare clean on the changed files.