Hmbown/CodeWhale · error

Failed to call DeepSeek Chat API: HTTP {status}: {error_text

Error message

Failed to call DeepSeek Chat API: HTTP {status}: {error_text}

What it means

The non-streaming DeepSeek Chat API call returned a non-2xx status. The response body is bounded to `ERROR_BODY_MAX_BYTES` and passed through `sanitize_http_error_body` before formatting, and the status is recorded via `record_provider_response`. The sanitized provider message is embedded verbatim.

Source

Thrown at crates/tui/src/client/chat.rs:1141

            None
        };

        // The endpoint was resolved by the shared seam alongside the body, so
        // a route-shape decision (e.g. DeepSeek's strict-tools `/beta` path)
        // cannot be made twice with two different answers.
        let url = prepared.endpoint.url.as_str();
        let response = self.send_json_with_retry(url, body).await?;

        let status = response.status();
        crate::client::record_provider_response(self.api_provider, status.as_u16());
        if !status.is_success() {
            let raw_error_text = bounded_error_text(response, ERROR_BODY_MAX_BYTES).await;
            let error_text = sanitize_http_error_body(
                Some(self.api_provider.display_name()),
                status.as_u16(),
                &raw_error_text,
            );
            anyhow::bail!("Failed to call DeepSeek Chat API: HTTP {status}: {error_text}");
        }

        let response_text = response
            .text()
            .await
            .context("Failed to read Chat API response body")?;
        let value: Value =
            serde_json::from_str(&response_text).context("Failed to parse Chat API JSON")?;
        let parsed = parse_chat_message_for_route(&value, self.api_provider, &self.base_url)?;
        if let Some(key) = response_cache_key {
            crate::llm_response_cache::response_cache().put(key, parsed.clone());
        }
        Ok(parsed)
    }
}

impl DeepSeekClient {
    async fn open_chat_stream_response(

View on GitHub (pinned to 8880682c63)

Solutions

  1. Read the HTTP status and embedded error text first — they map directly to the cause
  2. 401/402: fix the API key or top up the DeepSeek account balance
  3. 429: back off and retry; reduce request frequency
  4. 404/400: verify the model ID and base URL against DeepSeek's current docs
Defensive patterns

Strategy: retry

Type guard

fn is_retryable_http_status_from_message(msg: &str) -> bool {
    [408, 409, 429, 500, 502, 503, 504]
        .iter()
        .any(|s| msg.contains(&format!("HTTP {s}")))
}

Try / catch

match client.send(request).await {
    Err(e) if is_retryable_http_status_from_message(&e.to_string()) => {
        backoff_retry(client, request, MAX_ATTEMPTS).await
    }
    Err(e) if e.to_string().contains("HTTP 401") || e.to_string().contains("HTTP 402") => {
        Err(e) // credential/balance: never retry, surface to the user
    }
    result => result,
}

Prevention

When it happens

Trigger: 401 invalid API key; 402 insufficient DeepSeek balance; 404 wrong model name or base URL; 422 invalid parameters; 429 rate limit; 500/503 provider errors.

Common situations: Expired or mistyped API key; drained DeepSeek account balance; renamed/deprecated model IDs; misconfigured base URL pointing at the wrong endpoint; bursts hitting rate limits.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/8bb59d7222c4f0bb. Report an issue: GitHub.