From 2a450ac753b8aa7d02db89019d0894ca98ed0139 Mon Sep 17 00:00:00 2001 From: Alex Kesling Date: Thu, 25 Jun 2026 10:07:56 -0400 Subject: [PATCH] fix(cli): surface the server's error message on a failed upload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Declared error responses (progenitor's ErrorResponse variant) were matched on status code only; the catch-all arm printed just "(HTTP {code})" and dropped the ApiErrorResponse body. A 400 — e.g. the upload validation that names duplicate step IDs — surfaced as a bare "failed (HTTP 400)" with no detail. Read .into_inner().error and include it, for both authenticated and anon uploads. The actionable 401/413/429 hints are preserved. --- crates/path-cli/src/cmd_pathbase.rs | 53 +++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/crates/path-cli/src/cmd_pathbase.rs b/crates/path-cli/src/cmd_pathbase.rs index 6d8377a..59d0ff3 100644 --- a/crates/path-cli/src/cmd_pathbase.rs +++ b/crates/path-cli/src/cmd_pathbase.rs @@ -407,13 +407,25 @@ pub(crate) fn anon_graphs_post(base_url: &str, document_json: &str) -> Result match resp.status().as_u16() { - 413 => bail!( - "anon upload exceeds the size cap — log in (`path auth login`) for a listable upload without that limit" - ), - 429 => bail!("anon upload rate-limited; retry shortly or log in"), - code => bail!("anon upload failed (HTTP {code})"), - }, + Err(pathbase_client::Error::ErrorResponse(resp)) => { + let code = resp.status().as_u16(); + match code { + 413 => bail!( + "anon upload exceeds the size cap — log in (`path auth login`) for a listable upload without that limit" + ), + 429 => bail!("anon upload rate-limited; retry shortly or log in"), + _ => { + // Surface the server's `ApiErrorResponse.error` (e.g. the + // 400 naming duplicate step IDs) instead of just the code. + let msg = resp.into_inner().error; + if msg.is_empty() { + bail!("anon upload failed (HTTP {code})") + } else { + bail!("anon upload failed (HTTP {code}): {msg}") + } + } + } + } Err(pathbase_client::Error::UnexpectedResponse(resp)) => { let status = resp.status(); let body = block_on(resp.text()).unwrap_or_default(); @@ -467,14 +479,25 @@ pub(crate) fn graphs_post( visibility: inner.visibility, }) } - Err(pathbase_client::Error::ErrorResponse(resp)) => match resp.status().as_u16() { - 401 => bail!( - "{base_url} rejected your stored credentials (HTTP 401). \ - Run `path auth login --url {base_url}` to authenticate against this server, \ - or pass `--anon` to upload anonymously." - ), - code => bail!("upload to {owner}/{repo} failed (HTTP {code})"), - }, + Err(pathbase_client::Error::ErrorResponse(resp)) => { + let code = resp.status().as_u16(); + if code == 401 { + bail!( + "{base_url} rejected your stored credentials (HTTP 401). \ + Run `path auth login --url {base_url}` to authenticate against this server, \ + or pass `--anon` to upload anonymously." + ) + } + // Declared error responses (e.g. the 400 from duplicate step IDs) + // carry an `ApiErrorResponse { code, error }` body — surface the + // human-readable `error` rather than just the status code. + let msg = resp.into_inner().error; + if msg.is_empty() { + bail!("upload to {owner}/{repo} failed (HTTP {code})") + } else { + bail!("upload to {owner}/{repo} failed (HTTP {code}): {msg}") + } + } Err(pathbase_client::Error::UnexpectedResponse(resp)) => { let status = resp.status(); let body = block_on(resp.text()).unwrap_or_default();