cjpais/Handy · error · anyhow::Error
server advertises bytes, expected
Error message
server advertises {} bytes, expected {} What it means
Raised when the catalog pins an expected size and the server's advertised remaining length (Content-Length plus the resume offset) does not sum to that size, before any bytes are written. The guard exists because writing to a mis-sized expectation either corrupts the file or masks truncation. Nothing has been written yet, but unlike some siblings this path does not delete the partial — the caller can retry once state is consistent.
Solutions
- Retry the download; if the catalog entry is stale it will keep failing — update the app so the refreshed catalog is used.
- Delete the model's partial and cached entry, then re-download from scratch.
- Try the alternate source (mirror vs. primary) in case only one host has the divergent object.
- Report the model id if both hosts disagree with the pinned size — it is a catalog bug.
Defensive patterns
Strategy: retry
Try / catch
match manager.download_model(model_id, None).await {
Ok(_) => Ok(()),
Err(e) if e.to_string().contains("server advertises") => {
// size metadata disagrees; drop local state and retry fresh
let _ = std::fs::remove_file(partial_path);
manager.download_model(model_id, None).await
}
Err(e) => Err(e),
} Prevention
- Pin expected sizes in the catalog and verify against Content-Length before writing (as done here).
- Regenerate catalog size+hash together whenever the artifact changes.
- Distrust and restart (not resume) whenever a size disagreement appears — an inconsistent server cannot be trusted for appends.
When it happens
Trigger: Resuming a download where the server now hosts a different-sized object (rebuilt artifact) while the catalog still pins the old size; servers advertising compressed transfer-length instead of object-length.
Common situations: Upstream re-published/repacked the model file after the catalog snapshot; catalog size entries going stale across releases.
Related errors
- download incomplete: expected
- no response within s from
- server sent more than the expected
- Complete model file not found
- Download failed from Hugging Face
AI-assisted analysis of cjpais/Handy@c89b7bf389 (2026-08-17).
Data as JSON: /api/errors/52a302b503dbfbfb.
Report an issue: GitHub.
Appendix: source
Thrown at src-tauri/src/managers/model/download.rs:285
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_from
));
}
}
// When the catalog pins the size, a server advertising a different
// total is already misbehaving — reject before writing anything.
if let (Some(expected), Some(len)) = (expected_size, response.content_length()) {
if resume_from + len != expected {
return Err(anyhow::anyhow!(
"server advertises {} bytes, expected {}",
resume_from + len,
expected
));
}
}
let known_total =
expected_size.or_else(|| response.content_length().map(|l| resume_from + l));
let total_size = known_total.unwrap_or(0);
let mut downloaded = resume_from;
let mut file = if resume_from > 0 {
std::fs::OpenOptions::new()
.append(true)
.open(partial_path)?
} else {
std::fs::File::create(partial_path)?
};View on GitHub (pinned to c89b7bf389)