Hmbown/CodeWhale · error · anyhow::Error

Anthropic API error (HTTP )

Error message

Anthropic API error (HTTP {status} {error_type}): {message}

What it means

Shared checker for Anthropic Messages requests (both one-shot and streaming): when the HTTP response is non-success, the bounded raw body is parsed by `parse_anthropic_error_envelope` into Anthropic's `error.type` and `error.message`, the request failure is recorded in the breaker, and the pair is re-raised with the HTTP status. This is the canonical surface for all upstream Anthropic errors (overloaded, invalid_request, auth, permission, rate limit).

Solutions

  1. Read `error_type` and `message` in the error: 401 → fix the API key; 404 → fix the model id; 429 → back off; 529 → retry later.
  2. For 400 invalid_request_error, inspect the message for which field/tool schema the API rejected.
  3. Enable retry/backoff for 429/529 statuses; the failure is also recorded via `mark_request_failure` for circuit-breaker handling.
  4. Verify the account has access to the requested Anthropic model.
Defensive patterns

Strategy: retry

Validate before calling

fn anthropic_error_is_retryable(status: u16, error_type: &str) -> bool {
    status == 429 || status == 529 || status >= 500 || error_type == "overloaded_error"
}

Try / catch

match client.send_anthropic_request(req).await {
    Err(e) if e.to_string().starts_with("Anthropic API error") => {
        let (status, etype) = parse_anthropic_error(&e.to_string());
        if anthropic_error_is_retryable(status, &etype) {
            retry_with_exponential_backoff().await
        } else {
            surface_to_user(&e) // 401 key, 404 model, 400 payload
        }
    }
    other => other,
}

Prevention

When it happens

Trigger: Any non-success status from the Anthropic Messages endpoint — 400 invalid_request_error (bad payload/tool schema), 401 authentication_error (bad API key), 403 permission_error, 404 not_found_error (bad model name), 413/429 request_too_large/rate_limit_error, 529/500 overloaded_error (Anthropic capacity).

Common situations: Expired or wrong `ANTHROPIC_API_KEY`; requesting a model id the key cannot access; tool-use payloads failing schema validation; hitting Anthropic's `overloaded_error` during capacity events; context length exceeded (invalid_request).

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/1f24b3b238f29655. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/client/anthropic.rs:242

            .send()
            .await
            .context("Anthropic Messages API request failed")?;
        self.check_anthropic_response(response).await
    }

    /// Shared status/error-envelope handling for streaming and
    /// non-streaming Messages responses.
    async fn check_anthropic_response(
        &self,
        response: reqwest::Response,
    ) -> Result<reqwest::Response> {
        let status = response.status();
        if !status.is_success() {
            let raw = bounded_error_text(response, ERROR_BODY_MAX_BYTES).await;
            let (error_type, message) = parse_anthropic_error_envelope(&raw);
            self.mark_request_failure(&format!("anthropic status={status}"))
                .await;
            anyhow::bail!("Anthropic API error (HTTP {status} {error_type}): {message}");
        }
        self.mark_request_success().await;
        Ok(response)
    }

    /// Open the streaming Messages request through the shared stream-entry
    /// transport policy: bounded header wait, dual-client selection, and at
    /// most one HTTP/1.1 fallback retry on a classified H2 header stall.
    /// Wire-specific request construction (headers, endpoint, body) stays
    /// here at the adapter edge.
    async fn open_anthropic_stream_response(
        &self,
        url: &str,
        body: &Value,
    ) -> Result<reqwest::Response> {
        let url = self.messages_transport_url(url);
        let open_req = super::stream_entry::StreamOpenRequest::new(
            super::stream_entry::stream_open_timeout(),

View on GitHub (pinned to 73e0f67d83)