cjpais/Handy · error · anyhow::Error
server returned HTTP {}
Error message
server returned HTTP {} What it means
Generic non-success HTTP status on the download GET, after the specialized 416 and 200-to-Range handling. The actual status code is embedded in the message. The request reached the server, but the server refused it or failed — the reqwest error layer for connect/DNS failures is separate from this check.
Source
Thrown at src-tauri/src/managers/model/download.rs:259
// 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)"
));
}
Self::verify_file_with_events(model_id, partial_path, expected_sha256, emit).await?;
return Ok(HttpDownloadOutcome::Completed);
}
// A 200 to a Range request means the server ignored it and is sending
// the whole file; appending it to the partial would corrupt the model.
if resume_from > 0 && response.status() == reqwest::StatusCode::OK {
let _ = fs::remove_file(partial_path);
resume_from = 0;
}
if !response.status().is_success() {
return Err(anyhow::anyhow!(
"server returned HTTP {}",
response.status()
));
}
// On a 206, trust but verify the offset: a reply starting anywhere but
// exactly our partial's end would silently corrupt the file on append.
if resume_from > 0 && response.status() == reqwest::StatusCode::PARTIAL_CONTENT {
let starts_at = response
.headers()
.get(reqwest::header::CONTENT_RANGE)
.and_then(|v| v.to_str().ok())
.and_then(content_range_start);
if starts_at != Some(resume_from) {
let _ = fs::remove_file(partial_path);
return Err(anyhow::anyhow!(
"server returned Content-Range starting at {:?}, expected {}",
starts_at,
resume_fromView on GitHub (pinned to 98a4d80cce)
Solutions
- Read the embedded status: 429/5xx — wait and retry with backoff; 401/403 — authenticate or switch to an open source; 404 — the URL is wrong, update the catalog or the app
- Confirm the URL responds: curl -I <url>
- Switch the model source to the official HuggingFace endpoint
- Update Handy — catalog URL fixes ship with app releases
Defensive patterns
Strategy: retry
Validate before calling
async fn download_url_ok(url: &str) -> bool {
match reqwest::Client::new().head(url).send().await {
Ok(resp) => resp.status().is_success(),
Err(_) => false,
}
} Try / catch
match downloader.download_http_resumable(...).await {
Ok(outcome) => Ok(outcome),
Err(e) if e.to_string().starts_with("server returned HTTP") => {
let status = e.to_string();
if status.contains("429") || status.contains("50") {
tokio::time::sleep(backoff).await; // transient: back off and retry
downloader.download_http_resumable(...).await
} else {
Err(e) // 403/404: fix the URL/source instead
}
}
Err(e) => Err(e),
} Prevention
- Add a preflight status check before launching multi-GB downloads
- Distinguish transient (429/5xx) from permanent (401/403/404) statuses in handlers
- Keep catalog URLs pointing at stable, official endpoints
When it happens
Trigger: 404/410 for dead or removed model URLs; 401/403 for gated HuggingFace repos or expired pre-signed URLs; 429 rate limiting; 5xx server outages; 451 geo-blocks.
Common situations: HF repo made private or deleted after the catalog shipped; mirror link rot; rate limits while re-downloading large models; CDN incidents.
Related errors
- transfer stalled: no progress for {}s
- Hugging Face download failed after {} attempt(s): {}
- Download failed from Hugging Face ({}) and {} mirror(s)
- Failed to verify download for model {}: {}. Please retry.
- no response within {}s from {}
AI-assisted analysis of cjpais/Handy@98a4d80cce (2026-08-16).
Data as JSON: /api/errors/30d5fba592996a4e.
Report an issue: GitHub.