cjpais/Handy · error · anyhow::Error
server object ends before the expected size (HTTP 416)
Error message
server object ends before the expected size (HTTP 416)
What it means
Raised when a Range resume request gets HTTP 416 (Range Not Satisfiable) and the server's object provably ends before the expected size — with a catalog-pinned expected_size, or with no hash that could vouch for the partial. The server is saying 'my file ends at or before where your partial begins', so the partial can never be completed; it is deleted and a clean restart is the only safe path.
Solutions
- Retry — the partial is already deleted, so the next attempt downloads fresh from byte 0.
- If it repeats immediately, the catalog metadata (expected size) disagrees with the server's actual object — switch to the primary URL or report the catalog entry.
- Clear the model's partial files manually only if the app cannot (unusual; deletion already happened here).
- Confirm no local clock/state weirdness by deleting the model entry entirely and re-adding it from the current catalog.
Defensive patterns
Strategy: retry
Try / catch
match manager.download_model(model_id, None).await {
Ok(_) => Ok(()),
Err(e) if e.to_string().contains("HTTP 416") => {
// partial deleted automatically; a fresh attempt re-downloads from zero
manager.download_model(model_id, None).await
}
Err(e) => Err(e),
} Prevention
- Keep catalog metadata (size/hash) in sync with the actual hosted artifact — publish catalog updates with file changes.
- Treat 416-on-resume as 'upstream changed' and prefer a full restart over clever partial salvage.
- When hosting models yourself, never replace an object in place at a stable URL; publish under a new path.
When it happens
Trigger: Resuming from a large partial against a server whose object shrank or was replaced by a smaller one; a catalog entry whose expected_size is larger than the file the mirror actually hosts; stale partials after the upstream re-published a smaller model.
Common situations: Mirror divergence: primary host re-uploaded a smaller/repacked artifact while the catalog kept the old size; interrupted downloads resumed days later after the file changed upstream.
Understand the failure class
- HTTP status errors: handling 4xx and 5xx responses — how to handle 4xx and 5xx responses properly.
Related errors
- Complete model file not found
- download incomplete: expected
- Download verification failed for model
- Model is currently downloading
- server returned Content-Range starting at
AI-assisted analysis of cjpais/Handy@c89b7bf389 (2026-08-17).
Data as JSON: /api/errors/858070e3eb8436c7.
Report an issue: GitHub.
Appendix: source
Thrown at src-tauri/src/managers/model/download.rs:245
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)"
));
}
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()
));
}View on GitHub (pinned to c89b7bf389)