Hmbown/CodeWhale · error · anyhow::Error

err.last_error

Error message

err.last_error

What it means

After exhausting the bounded transport retry policy, the client rethrows the underlying error via anyhow::Error::new(err.last_error). The final error a caller sees is the last LlmError from the request loop, with its structured type preserved for downcasting so auth/rate-limit/invalid-request failures stay classifiable (#3884).

Solutions

  1. Downcast the anyhow error to LlmError to classify auth vs rate-limit vs network before deciding on user messaging
  2. Check the provider status / API key validity indicated by the underlying LlmError variant
  3. If transient, wait for the recovery probe to restore the connection and retry

Example fix

// before
if let Err(e) = client.send(req).await { log::error!("{}", e); }
// after
if let Err(e) = client.send(req).await {
    if let Some(llm) = e.downcast_ref::<LlmError>() {
        match llm { LlmError::Auth(_) => reauth(), LlmError::RateLimit(_) => backoff(), _ => {} }
    }
}
Defensive patterns

Strategy: try-catch

Type guard

fn classify(e: &anyhow::Error) -> Option<&LlmError> { e.downcast_ref::<LlmError>() }

Try / catch

match client.send(req).await {
    Err(e) => match e.downcast_ref::<LlmError>() {
        Some(LlmError::Auth(_)) => reauthenticate(),
        Some(LlmError::RateLimit(_)) => schedule_backoff(),
        _ => report_transport_failure(&e),
    },
    ok => ok,
}

Prevention

When it happens

Trigger: Any LLM request that fails on every retry attempt: mark_request_failure has been called and maybe_probe_recovery has run, then the last_error is surfaced to the caller.

Common situations: Provider outage lasting longer than the retry window; persistent 401/403 from a revoked API key; sustained rate limiting; malformed requests rejected on every attempt.

Understand the failure class

Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/6f69664c66651ec9. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/client.rs:3506

            Err(err) => {
                if let LlmError::RateLimited { retry_after, .. } = &err.last_error {
                    crate::retry_status::note_rate_limit(
                        retry_after
                            .unwrap_or_else(|| retry_cfg.delay_for_attempt(retry_cfg.max_retries)),
                    );
                }
                let last = err.last_error.to_string();
                if err.attempts > 1 {
                    crate::retry_status::failed(last.clone());
                } else {
                    crate::retry_status::clear();
                }
                self.mark_request_failure(&last).await;
                self.maybe_probe_recovery().await;
                // Keep the structured `LlmError` downcastable so failure
                // surfaces can classify auth/rate-limit/invalid-request
                // instead of reporting an opaque string (#3884).
                Err(anyhow::Error::new(err.last_error))
            }
        }
    }

    /// The same bounded transport retry policy without process-global retry
    /// banners, provider-wide pause cells, or shared connection-health writes.
    /// Used only by the Auto classifier during read-only request inspection.
    async fn send_with_isolated_retry<F>(
        &self,
        mut build: F,
        include_error_body: bool,
    ) -> Result<reqwest::Response>
    where
        F: FnMut() -> reqwest::RequestBuilder,
    {
        let retry_cfg: LlmRetryConfig = self.retry.clone().into();
        let request_result = with_retry(
            &retry_cfg,

View on GitHub (pinned to 433685b202)