Route async API failures through the body-preserving classifier and add run-create context so CLI output keeps server response details in the cause chain.
4.3 KiB
Improve CLI API Error Display Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Preserve useful server error details in Fabro CLI output without adding a new error taxonomy or sprawling command-specific handling.
Architecture: Keep error flow centered on existing anyhow, source(), miette, TaggedFailure, ApiFailure, api_failure_for, classify_api_error, and raw_response_failure_error abstractions. Preserve response details centrally in fabro-client, then add sparse user-action context at command boundaries.
Tech Stack: Rust, anyhow, miette, progenitor_client, httpmock, cargo nextest.
Summary
Improve failed fabro run and related API command output by preserving server response details through the existing error chain. The target display for run creation failures is:
x could not create run
caused by: missing field `dirty` at line 1 column 2834
For non-JSON plain-text bodies, the CLI should retain the body instead of collapsing to status only:
x could not create run
caused by: request failed with status 422 Unprocessable Entity: Failed to deserialize ...
Key Changes
- In
lib/crates/fabro-client/src/error.rs, keepmap_api_errorsynchronous. Do not make it readUnexpectedResponsebodies, because that requires async body consumption. - In async client paths that receive
progenitor_client::Errorafter.await, useclassify_api_error(err).awaitsoUnexpectedResponsebodies are consumed and preserved. - Update the token-refresh retry path in
Client::send_apiso retry failures also go through the async classifier instead of.map_err(map_api_error). - Update optional fetch paths that currently call
map_api_errorafter.await(get_run_logs,read_run_blob) to useclassify_api_error(err).await.error, preserving existingis_not_found_errorbehavior throughApiFailure. - In
lib/crates/fabro-cli/src/commands/run/create.rs, wrapclient.create_run_from_manifest(built.manifest).awaitwithcontext("could not create run"). - Do not add new public error types, new CLI diagnostic enums, run-specific API error branches, or a new version-compatibility framework.
- Do not change server wire behavior in this patch.
Interface Impact
- No public Rust API additions.
- No OpenAPI/schema changes.
- CLI stderr output changes for failed API calls by showing action context plus the existing source chain.
- Exit codes remain unchanged because
ApiFailureand existingExitClasstagging stay in place. --jsonbehavior remains unchanged; do not add new JSON error payloads.
Test Plan
- Add or adjust
fabro-clientunit tests inlib/crates/fabro-client/src/error.rsfor structured JSON API errors:errors[0].detailremains the displayed message.errors[0].coderemains discoverable throughapi_failure_for.- Existing 401
ExitClass::AuthRequiredbehavior still passes.
- Add a
fabro-clientasync test for a plain-text422UnexpectedResponsethroughclassify_api_error:- The displayed error includes both the status and response body.
api_failure_forreports status422.
- Add a CLI integration test in
lib/crates/fabro-cli/tests/it/cmd/run.rswith a mock server returning422fromPOST /api/v1/runs:stderrincludescould not create run.stderrincludes the response detail/body, such asmissing field \dirty``.stderrdoes not collapse to status-only output.
- Run targeted tests:
cargo nextest run -p fabro-client
cargo nextest run -p fabro-cli --test it run
- Run workspace checks:
cargo +nightly-2026-04-14 fmt --check --all
cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings
Assumptions
- The first fix should improve error presentation, not add backwards compatibility for the
git.dirtymanifest change. - Version-skew-specific hints are out of scope unless they can reuse already-available metadata without extra probing or new error plumbing.
- Existing
miettecause-chain rendering is the display mechanism; implementation should make the error chain better, not bypass it.