Hmbown/CodeWhale · error · anyhow::Error
DS4 /v1/models could not be reached at
Error message
DS4 /v1/models could not be reached at {} (is ds4-server running?) What it means
Produced by ds4_probe_error during doctor's probe_ds4_models: list_models failed with a connection-level error that contains no HTTP status token — the request never got an HTTP response. The message explicitly asks whether ds4-server is running and prints the redacted endpoint.
Solutions
- Start ds4-server and confirm it listens on the configured host and port
- Verify deepseek_base_url (scheme, host, port) matches the server's actual bind address
- Test reachability directly, e.g. curl the /v1/models URL from the same machine
Example fix
# before deepseek_base_url = "http://127.0.0.1:9000" # after (server listens on 8080) deepseek_base_url = "http://127.0.0.1:8080"
Defensive patterns
Strategy: retry
Validate before calling
// Cheap pre-check that something is listening before the probe:
let url = config.deepseek_base_url();
let host_port = parse_host_port(&url)?;
if !tcp_connectable(host_port, std::time::Duration::from_secs(1)).await {
return Err(anyhow::anyhow!("ds4 endpoint {host_port} is not accepting connections; start ds4-server"));
} Try / catch
match probe_ds4_models(&config).await {
Ok(()) => {}
Err(error) if error.to_string().contains("could not be reached") => {
// start ds4-server (or fix the URL), then retry the probe once
}
Err(error) => return Err(error),
} Prevention
- Start ds4-server before running doctor and verify the port matches deepseek_base_url
- Prefer a loopback base URL for local servers to avoid firewall/DNS surprises
- Keep one canonical base URL in config instead of per-tool copies that drift
When it happens
Trigger: Nothing listening on the configured host:port (connection refused), DNS resolution failure for the base URL host, or a TLS handshake failure on an https base URL.
Common situations: ds4-server not started or crashed; wrong port in deepseek_base_url; firewall or VPN blocking localhost/remote endpoints; https pointed at an http listener.
Understand the failure class
Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.
Related errors
- DS4 /v1/models timed out after 15 seconds at
- ds4_probe_error(config, &error.to_string())
- DS4 /v1/models at did not list configured alias ' '…
- DS4 /v1/models returned HTTP
- err.last_error
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/4e9b0343b32b8d61.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/doctor.rs:583
}
/// 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 0c42157ee5)