cjpais/Handy · error · anyhow::Error
no response within {}s from {}
Error message
no response within {}s from {} What it means
The initial GET (with an optional Range header for resume) did not produce a response within DOWNLOAD_STALL_TIMEOUT — 60 seconds, defined at download.rs:26 (a separate 15s HTTP_CONNECT_TIMEOUT governs connection setup). The downloader refuses to wait indefinitely for headers; the cancel token is honored concurrently via tokio::select!.
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 98a4d80cce)
Solutions
- Retry — the download is resumable and often succeeds on a healthier route
- Verify reachability from the same machine: curl -I --max-time 70 <url>
- Switch the model source (e.g. to the official HuggingFace repo) if a mirror consistently stalls
- If you control a legitimately slow host, raise DOWNLOAD_STALL_TIMEOUT in download.rs
Defensive patterns
Strategy: retry
Validate before calling
// Cheap preflight before committing to a long download
async fn url_responsive(url: &str) -> bool {
reqwest::Client::builder()
.connect_timeout(Duration::from_secs(15))
.build()
.unwrap()
.head(url)
.send()
.await
.map(|r| r.status().is_success() || r.status().as_u16() == 206)
.unwrap_or(false)
} Try / catch
let mut backoff = Duration::from_secs(2);
for _ in 0..3 {
match downloader.download_http_resumable(...).await {
Ok(outcome) => break Ok(outcome),
Err(e) if e.to_string().starts_with("no response within") => {
tokio::time::sleep(backoff).await; // exponential backoff
backoff *= 2;
continue; // resume keeps progress
}
Err(e) => break Err(e),
}
} Prevention
- Mirror the 60s stall budget in any preflight health check before starting big downloads
- Prefer resumable endpoints so timeouts never restart from byte zero
- For slow private hosts, size DOWNLOAD_STALL_TIMEOUT to the worst realistic header latency
When it happens
Trigger: Server or CDN slow or hung before sending response headers; network black-holed after connect (VPN drop, captive portal); DNS+TLS+headers exceeding 60s on very slow links; a dead mirror that accepts connections then stalls.
Common situations: Corporate proxies that hold requests; overloaded mirror hosts; mobile/tethered networks with long round trips; firewalls silently dropping packets.
Related errors
- transfer stalled: no progress for {}s
- Hugging Face download failed after {} attempt(s): {}
- Download failed from Hugging Face ({}) and {} mirror(s)
- transfer stalled: no data for {}s
- {:?}
AI-assisted analysis of cjpais/Handy@98a4d80cce (2026-08-16).
Data as JSON: /api/errors/1afb3731c900b2ff.
Report an issue: GitHub.