Hmbown/CodeWhale · error

DS4 /v1/models timed out after 15 seconds at

Error message

DS4 /v1/models timed out after 15 seconds at {}

What it means

In `probe_ds4_models`, the `list_models` call is wrapped in a 15-second timeout via `tokio::time::timeout`. When the future does not complete in time (the `Err(_)` arm), the probe bails with a timeout message naming the endpoint. The endpoint answered too slowly or not at all.

Solutions

  1. Confirm the endpoint is responsive with `curl --max-time 15 <endpoint>/v1/models` and restart the DS4 server if it hangs.
  2. Check network/firewall rules between this machine and the DS4 endpoint.
  3. Verify DNS resolves the endpoint host to the correct reachable address.
  4. If the endpoint is genuinely slow, reduce load or move it closer; the 15-second probe budget is fixed.
Defensive patterns

Strategy: retry

Validate before calling

curl -sS --max-time 15 "$DS4_ENDPOINT/v1/models" >/dev/null && echo reachable || echo unreachable

Try / catch

for attempt in 0..3 {
    match probe_ds4_models().await {
        Err(e) if e.to_string().contains("timed out") => tokio::time::sleep(Duration::from_secs(2u64.pow(attempt))).await,
        other => { other?; break; }
    }
}

Prevention

When it happens

Trigger: Doctor DS4 probe where `client.list_models()` takes longer than 15 seconds — hung connection, unresponsive server, or network black hole (packets dropped rather than refused).

Common situations: DS4 server overloaded or stuck; firewall silently dropping traffic to the endpoint; DNS resolving to an unreachable host; remote endpoint with very high latency.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


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

Appendix: source

Thrown at crates/tui/src/doctor.rs:580

/// 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::CodewhaleClient;
    use crate::core::model_client::ModelClient;

    let endpoint = crate::client::redact_url_for_display(&config.active_route_base_url());
    let client = CodewhaleClient::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 {
                advertised.as_str()

View on GitHub (pinned to 73e0f67d83)