cjpais/Handy · error · anyhow::Error

no response within s from

Error message

no response within {}s from {}

What it means

Raised when the HTTP request to start (or resume) a model download produces no response within DOWNLOAD_STALL_TIMEOUT (60 s, racing a 15 s connect timeout). The tokio::select! also races the cancel token, so this error specifically means: not cancelled, but the server never finished sending response headers in time. Any partial file on disk is kept, so a subsequent download call resumes instead of restarting.

Solutions

  1. Retry the download — the resumable downloader continues from the kept partial where one exists.
  2. Check basic reachability of the URL (curl -I <url>) from the same machine/network.
  3. Disable/adjust VPN or proxy if it intercepts the model hosts.
  4. Switch to a different mirror/URL for the model if the primary host consistently times out.
  5. If on restricted networks (corporate/school), allow-list the download domains.
Defensive patterns

Strategy: retry

Validate before calling

// Cheap pre-flight before a long download
let client = reqwest::Client::builder().timeout(Duration::from_secs(20)).build()?;
let resp = client.head(url).send().await?;
if !resp.status().is_success() {
    anyhow::bail!("download source unhealthy: HTTP {}", resp.status());
}

Try / catch

match manager.download_model(model_id, None).await {
    Ok(_) => Ok(()),
    Err(e) if e.to_string().contains("no response within") => {
        // partial is preserved; retry resumes instead of restarting
        manager.download_model(model_id, None).await
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: request.send() exceeds 60 s: unreachable/black-holing host (packets dropped after connect), proxy that accepts the connection but never answers, server headers delayed behind a slow backend, or DNS+connect to a dead mirror exceeding the window.

Common situations: Firewalled networks blocking blob/HF hosts; VPN split-tunnel dropping traffic to CDNs; overloaded mirrors; captive portals that hold connections open; flaky mobile tethers.

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 cjpais/Handy@c89b7bf389 (2026-08-17). Data as JSON: /api/errors/1afb3731c900b2ff. Report an issue: GitHub.

Appendix: source

Thrown at src-tauri/src/managers/model/download.rs:228

        if resume_from > 0 {
            info!(
                "Resuming download of {} from byte {}",
                model_id, resume_from
            );
        } else {
            info!("Starting fresh download of {} from {}", model_id, url);
        }

        let client = reqwest::Client::builder()
            .connect_timeout(HTTP_CONNECT_TIMEOUT)
            .build()?;
        let mut request = client.get(url);
        if resume_from > 0 {
            request = request.header("Range", format!("bytes={}-", resume_from));
        }
        let response = tokio::select! {
            r = tokio::time::timeout(DOWNLOAD_STALL_TIMEOUT, request.send()) => r
                .map_err(|_| anyhow::anyhow!(
                    "no response within {}s from {}",
                    DOWNLOAD_STALL_TIMEOUT.as_secs(), url
                ))??,
            _ = cancel_token.cancelled() => return Ok(HttpDownloadOutcome::Cancelled),
        };

        // 416 to our Range request means its start is at or past the object's
        // end. With a catalog size in hand that can only mean the server's
        // object is *smaller* than expected (a full-size partial never issues
        // a request — handled above), and with no hash there is no trusted
        // signal to bless the partial: both restart clean. Only a hash can
        // genuinely finish a partial here. Without a Range in flight a 416 is
        // just a broken server, which the generic status check below rejects.
        if resume_from > 0 && response.status() == reqwest::StatusCode::RANGE_NOT_SATISFIABLE {
            if expected_size.is_some() || expected_sha256.is_none() {
                let _ = fs::remove_file(partial_path);
                return Err(anyhow::anyhow!(
                    "server object ends before the expected size (HTTP 416)"

View on GitHub (pinned to c89b7bf389)