cjpais/Handy · error · anyhow::Error
transfer stalled: no data for
Error message
transfer stalled: no data for {}s What it means
Raised when the response body stream yields no chunk for DOWNLOAD_STALL_TIMEOUT (60 s) while the download is otherwise in progress. The connection is alive at the HTTP level but data stopped flowing — a mid-transfer stall. Critically, the partial file is kept on purpose, so the next download attempt resumes from where the data stopped rather than restarting a multi-GB fetch.
Solutions
- Retry the download — resume picks up from the preserved partial.
- Prevent the machine from sleeping during large downloads (or use a wired connection).
- If stalls repeat at the same point, capture the URL and test with curl -C - <url> to distinguish network from server issues.
- Move off unreliable hotspots/VPNs for multi-GB models.
Defensive patterns
Strategy: retry
Try / catch
match manager.download_model(model_id, None).await {
Ok(_) => Ok(()),
Err(e) if e.to_string().contains("transfer stalled") => {
// partial preserved on stall: the retry resumes from where data stopped
manager.download_model(model_id, None).await
}
Err(e) => Err(e),
} Prevention
- Disable system sleep during multi-GB downloads.
- Always keep the partial on stall-timeout (as this code does) so retries are cheap.
- Surface a resume affordance in the UI instead of forcing users to restart from zero.
- Use stable, wired networks for first-time large model fetches.
When it happens
Trigger: stream.next() times out: server/CDN stops sending mid-body, connection half-open after a NAT/firewall drop, path MTU blackhole, or severe bandwidth starvation (not zero, but under the event loop's progress cadence — practically, nothing arrives for 60 s).
Common situations: Laptop sleep/resume mid-download; flaky hotel/airport Wi-Fi; mobile tether switch; CDN node failure mid-stream; power-saving NICs dropping long transfers.
Related errors
- no response within s from
- transfer stalled: no progress for
- download incomplete: expected
- Hugging Face download failed after
- server object ends before the expected size (HTTP 416)
AI-assisted analysis of cjpais/Handy@c89b7bf389 (2026-08-17).
Data as JSON: /api/errors/540f74dc560ec733.
Report an issue: GitHub.
Appendix: source
Thrown at src-tauri/src/managers/model/download.rs:327
total: total_size,
percentage: if total_size > 0 {
(downloaded as f64 / total_size as f64) * 100.0
} else {
0.0
},
}));
};
emit_progress(downloaded);
// Throttle progress events to max 10/sec (100ms intervals)
let mut last_emit = Instant::now();
let throttle = Duration::from_millis(100);
let mut stream = response.bytes_stream();
loop {
let chunk = tokio::select! {
c = tokio::time::timeout(DOWNLOAD_STALL_TIMEOUT, stream.next()) => match c {
// Stalled mid-body: keep the partial for resume.
Err(_) => return Err(anyhow::anyhow!(
"transfer stalled: no data for {}s",
DOWNLOAD_STALL_TIMEOUT.as_secs()
)),
Ok(None) => break,
Ok(Some(chunk)) => chunk?,
},
_ = cancel_token.cancelled() => {
// Keep the partial for resume; caller handles state cleanup.
return Ok(HttpDownloadOutcome::Cancelled);
}
};
// An untrusted server must not be able to fill the disk: cut the
// transfer at the first byte past the known total instead of
// trusting it to eventually close the stream. Everything written
// so far is tainted by a provably-misbehaving server — clear it.
if let Some(cap) = known_total {
if downloaded + chunk.len() as u64 > cap {
drop(file);View on GitHub (pinned to c89b7bf389)