Repository navigation
fix: surface the manager's own error for every failed manager request - #976
Merged
Merged
Conversation
|
Enclave skipped this draft pull request. It will run automatically when you mark the PR as Ready for review. |
Member
Author
|
@greptileai review |
|
Member
Author
|
@greptileai review |
alongubkin
marked this pull request as ready for review
October 10, 2026 05:16
alongubkin
force-pushed
the
alon/alien-1379-manager-error-bodies
branch
from
October 10, 2026 05:59
efe613e to
106663e
Compare
Member
Author
|
@greptileai review |
The generated manager client kept only the HTTP status for most failed requests. A status the route's OpenAPI did not list arrived with its body unread, and the sync into_sdk_error never read it. A status listed without a body schema (401/403/404 on most routes) had its body dropped by the generated code. The body-reading adapter was opt-in per call site, so commands such as `alien deployments get` printed only "Unexpected response: 400 Bad Request". The progenitor input spec now declares no error responses, so every error status reaches the client with its body, and into_sdk_error is async and always reads it. The opt-in adapter and the typed error path are gone; there is one conversion for every route. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
renew_lease wrapped every manager error in GenericError, which is never retryable, so the renewal loop treated even a network blip as a lost lease and its retry-until-deadline branch could not run. Use ManagerRequestFailed, which inherits retryable from the source, as the checkpoint call already does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GENERIC_ERROR always carries retryable: false, including a server's sanitized "Internal server error" for a retryable internal cause. Reading error bodies made that flag decide retries, so a transient 500 ended alien deployments wait at once. For GENERIC_ERROR the HTTP status decides again; classified errors keep their own flag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
alongubkin
force-pushed
the
alon/alien-1379-manager-error-bodies
branch
from
October 10, 2026 07:36
106663e to
8bf968c
Compare
Member
Author
|
@greptileai review |
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.
Problem
alien deployments getprinted onlyUnexpected response: 400 Bad Requestwhen the manager answered with a structured error (code, message, source). Most other manager-backed commands had the same problem.Root cause
The progenitor-generated manager client keeps only the status for most failures:
GET /v1/deployments/{id}) arrives asError::UnexpectedResponsewith the body unread, and the syncSdkResultExt::into_sdk_errornever read it;Error::ErrorResponse(ResponseValue::empty(..)), so the generated code throws the body away.An async body-reading adapter (
into_sdk_error_reading_body) existed but was opt-in per call site. Earlier fixes converted one site at a time; about 60 sites still used the sync path.Alternatives considered
InvalidResponsePayload, which loses the status and breaks retry decisions.Fix
client-sdks/manager/scripts/fix-openapi.mjsremoves every non-2xx response from the progenitor input spec (openapi-3.0.json, used only by the Rust SDK; the 3.1 spec and TS SDK are unchanged). Every error status now reaches the client asUnexpectedResponsewith its body.SdkResultExt::into_sdk_erroris async and always reads the body: an Alien error JSON becomes that error (withrequestId); anything else givesUNEXPECTED_RESPONSEwith the status and retryability.SdkResultExtReadingBody,convert_sdk_error_reading_bodyand the typedError<types::AlienError>path are removed.convert_sdk_erroris now async..await, so the compiler found every one of them.retryableflag on every route. The checkpoint call has worked this way since fix: retry a deployment checkpoint after a transient manager error #801: a 500DEPLOYMENT_LEASE_LOSTwithretryable: falsemust not be retried.GENERIC_ERRORis the exception: it is always sent withretryable: false, including a server's sanitized "Internal server error" for a retryable cause such as a database error, so its flag is no decision and the HTTP status decides (5xx/429/408 retryable), as before this change. An unstructured 5xx, or a network error, stays retryable based on its status.renew_leasewrapped every error inGenericError, which is never retryable, so any renewal failure, even a refused connection, lost the lease right away. It now usesManagerRequestFailed, which inheritsretryable(the checkpoint call already does), so the renewal loop's retry-until-deadline path can run.generate:manager-rust-sdksteps aftergenerate:manager-openapi(the manager'sopenapi.jsonis unchanged): only error responses are removed.Proof
deployment_resolvertests run the realalien deployments get dep_…lookup against an HTTP mock. On main both fail (left: "UNEXPECTED_RESPONSE",right: "DEPLOYMENT_CONTEXT_UNAVAILABLE"/"DEPLOYMENT_NOT_FOUND"). On this branch both pass: the 400's code, message and source error reach the rendered CLI output, and a 404 keeps the manager's error while still mapping to "was not found".an_unreachable_manager_fails_the_renewal_as_retryableanda_renewal_the_manager_rejects_is_not_retriedpass; alien-deploymentmanager_api_transporttests 27/27.get_deploymentagainst a loopback server returns the server's error for both an unlisted 400 and a listed 404; a malformed HTML 500 still keeps status 500 and stays retryable.cargo check --workspace --all-targetsis clean. Lib tests pass for alien-manager-api (8), alien-cli (417), alien-deployment (174), alien-bindings (365) and alien-operator (92). The only failures were tests that need the sandbox environment (pnpm-built core, systemd), and they are unrelated to this change.node --test client-sdks/manager/scripts/fix-openapi.test.mjspasses.alien deployments wait: 503 empty, 502 HTML, retryable 500, refused or dropped connection, truncated body, non-UTF-8 and non-Alien JSON bodies all retry and succeed on both. A sanitizedGENERIC_ERROR500 ended the wait on the first response before theGENERIC_ERRORrule; with it, the wait retries and succeeds like main. SDK testsan_unclassified_server_error_is_retryable_by_its_statusanda_classified_server_error_keeps_its_own_retryable_flagcover the rule.Unexpected response: 400 Bad Request; a deleted deployment's 404, an invalid token's 401 and a forbidden 403 print the manager's message; list/get/wait/create/delete output is unchanged.Rollout
Client-only change. The
GENERIC_ERRORrule also applies to the deployment runner's manager calls: a sanitized 500 on a checkpoint is now retried with backoff (like a network error since #801) instead of failing the deployment, so a deterministic unclassified 500 there shows as a stuck step with retry warnings rather than a failed deployment. Downstream Rust users ofalien-manager-apineed.into_sdk_error().await. Code that matched on theUnexpected response: <status>message text should usehttp_status_codeinstead, because the message is now the server's own.🤖 Generated with Claude Code