Hmbown/CodeWhale · error
DS4 /v1/models returned HTTP {status} at {}
Error message
DS4 /v1/models returned HTTP {status} at {} What it means
Emitted by the TUI doctor's DS4 probe (crates/tui/src/doctor.rs). The probe builds a DeepSeekClient from config and calls `list_models()` under a 15-second timeout; transport errors go to `ds4_probe_error`, which scans the error text for an `HTTP <3-digit>` token (doctor.rs:436-458). When one is found, the doctor reports that `/v1/models` answered with that non-success status at the redacted configured base URL.
Source
Thrown at crates/tui/src/doctor.rs:410
}
/// Probe DS4 through its cheap `/v1/models` contract instead of waking the
/// model for a completion. The selected model must be advertised.
pub(crate) async fn probe_ds4_models(config: &crate::config::Config) -> anyhow::Result<()> {
use crate::client::DeepSeekClient;
use crate::core::model_client::ModelClient;
let endpoint = crate::client::redact_url_for_display(&config.deepseek_base_url());
let client = DeepSeekClient::new(config)?;
let configured_alias = client.model().to_string();
let models = match tokio::time::timeout(
std::time::Duration::from_secs(15),
client.list_models(),
)
.await
{
Ok(Ok(models)) => models,
Ok(Err(error)) => anyhow::bail!(ds4_probe_error(config, &error.to_string())),
Err(_) => anyhow::bail!("DS4 /v1/models timed out after 15 seconds at {}", endpoint),
};
if !models
.iter()
.any(|available| available.id == configured_alias)
{
let advertised = models
.iter()
.take(8)
.map(|available| available.id.as_str())
.collect::<Vec<_>>()
.join(", ");
anyhow::bail!(
"DS4 /v1/models at {} did not list configured alias '{configured_alias}' (advertised: {})",
endpoint,
if advertised.is_empty() {
"none"
} else {View on GitHub (pinned to 8880682c63)
Solutions
- Reproduce manually: `curl -i <deepseek_base_url>/models` with the configured key and read the raw status
- Fix the API key/credentials used by DeepSeekClient (config or env) for 401/403
- Correct `deepseek_base_url` (host, port, path) for 404
- Inspect ds4-server logs and restart it for 5xx responses
Defensive patterns
Strategy: validation
Validate before calling
let resp = reqwest::Client::new()
.get(format!("{}/models", config.deepseek_base_url()))
.bearer_auth(config.deepseek_api_key())
.send()
.await?;
if !resp.status().is_success() {
anyhow::bail!("ds4-server unhealthy: HTTP {}", resp.status());
}
// safe to run the doctor probe now Try / catch
match probe_ds4(config).await {
Ok(()) => {}
Err(err) if err.to_string().starts_with("DS4 /v1/models returned HTTP") => {
eprintln!("doctor: fix ds4-server auth or base URL: {err}");
}
Err(err) => return Err(err),
} Prevention
- Health-check ds4-server before running the doctor
- Manage the DeepSeek API key via a secret source so rotation does not strand stale values
- Pin deepseek_base_url to the exact server root; avoid ad-hoc port edits
When it happens
Trigger: ds4-server is reachable but returns 401/403 because the configured DeepSeek API key is wrong or missing, 404 because `deepseek_base_url` points at the wrong port or path, or 5xx while the server is failing or starting up.
Common situations: Rotated API key still in config; base URL pointing at an auth-gating proxy; a ds4-server version that gates `/v1/models`; server crashing mid-request.
Related errors
- DS4 /v1/models could not be reached at {} (is ds4-server run
- DeepSeek ${res.status}: ${text}
- Runtime API request failed (${status}): ${message}
- iLink API ${endpoint} failed: HTTP ${response.status} — ${te
- failed to fetch {description} from {url}: HTTP {status} {bod
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/db12e3d98582b868.
Report an issue: GitHub.