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
- Retry the download — the resumable downloader continues from the kept partial where one exists.
- Check basic reachability of the URL (curl -I <url>) from the same machine/network.
- Disable/adjust VPN or proxy if it intercepts the model hosts.
- Switch to a different mirror/URL for the model if the primary host consistently times out.
- 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
- Pre-flight the URL (HEAD request) before offering the download in the UI.
- Keep retry loops resumable — never delete the partial on timeout.
- Keep model hosts reachable: allow-list them on restricted networks.
- Avoid VPN/proxy paths that black-hole large transfers.
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
- transfer stalled: no data for
- transfer stalled: no progress for
- download incomplete: expected
- Hugging Face download failed after
- server advertises bytes, expected
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)