Repository navigation
fix: expose the assistant error kind on ResultError - #1471
BitmapAsset wants to merge 2 commits into
Conversation
A run that fails because the CLI is not logged in ends with the same
result shape as a transient stream timeout: subtype "success",
terminal_reason "api_error" and no api_error_status. The only field that
tells them apart is the `error` of the preceding assistant message
("authentication_failed"), and ResultError never carried it. The
docstring's example (`if e.terminal_reason == "api_error": retry()`)
therefore retries a logged-out headless worker forever.
Track the `error` of the latest top-level assistant message in Query and
pass it to ResultError as `error`. Update the docstring example to branch
on `e.error`. The attribute survives pickle/copy like the others.
|
Thanks for writing up the logged-out case so clearly, and for extending the pickle/copy tests to the new attribute. I ran this PR's head (
The value passes through at runtime, but under Mirroring the TS list is one hunk, here or in a separate PR, whichever you and the maintainers prefer: AssistantMessageError = Literal[
"authentication_failed",
+ "oauth_org_not_allowed",
+ "account_on_hold",
+ "verification_required",
"billing_error",
"rate_limit",
+ "overloaded",
"invalid_request",
+ "model_not_found",
"server_error",
"unknown",
+ "max_output_tokens",
+ "cloud_credential_error",
]With How I ran it
Output from this PR's head (retry frames trimmed): |
The CLI sends "overloaded" as an assistant error kind, so the docstring's retry branch should include it. Also note that ResultError.error is typed as str, so kinds newer than AssistantMessageError pass through and can be compared without a type error.
|
Thanks @kimnamu, this is really useful. The Bedrock 403 case is exactly the one this PR is for, and it's good to see the kind holds up on a second provider, including after the CLI's 261 s of retries. On the types: def on_result_error(e: ResultError) -> bool:
return e.error == "model_not_found" or e.error in ("rate_limit", "overloaded", "server_error") # passes
def on_assistant(m: AssistantMessage) -> bool:
return m.error == "model_not_found" # error: Non-overlapping equality check [comparison-overlap]So the error you hit comes from The docstring was misleading on this point, since it described the attribute as "an
|
Problem
When the CLI is not logged in, a headless
query()run ends like this (SDK 0.2.178, CLI 2.1.288, emptyCLAUDE_CONFIG_DIR, no credentials):That
ResultErrorlooks the same as a transient failure. The existing testtest_api_error_result_uses_result_text_not_success_subtypebuilds a stream idle timeout result with exactly these fields (subtype="success",terminal_reason="api_error",api_error_status=None). The only field that tells the two apart isAssistantMessage.error, which arrives on an earlier message and is not attached to the exception.The
ResultErrordocstring recommends:A logged-out worker (cron, CI, container) matches that branch and gets retried forever. To avoid that, callers have to string-match "Not logged in" or keep their own copy of the last assistant message, which is the kind of work
ResultErrorwas added to remove (#1205).Change
Queryremembers theerrorof the latest top-level assistant message (subagent messages are ignored) and passes it toResultErroras a new optionalerrorattribute (anAssistantMessageErrorvalue such as"authentication_failed","rate_limit"or"server_error", orNone).e.error: retry onrate_limit/server_error, stop onauthentication_failed.erroris a keyword argument with a default, sotype(e)(*e.args)reconstruction and existing callers keep working. It round-trips through pickle and copy like the other attributes.Tests
test_auth_failure_result_error_carries_assistant_error_kind: a mocked transport sends assistant(error="authentication_failed"), then an error result shaped like the timeout above, then exit 1. The test assertse.error == "authentication_failed". It fails with thequery.pychange reverted.error.pytest tests/: 1644 passed, 6 skipped.ruff check,ruff format --checkandmypy src/ scripts/are clean.The TypeScript Agent SDK has the same gap (it throws a plain
Erroron error results), but its source isn't in a public repo, so this PR covers Python only.