ducaale/xh · error
Content-Range has wrong start
Error message
Content-Range has wrong start: {:?} What it means
total_for_content_range validates a Content-Range header returned for a resumed download. After parsing the byte range and total, it checks that the first byte position matches the resume offset the client requested. A mismatch means the server did not resume the transfer from the expected offset.
Solutions
- Delete the partial download file and its resume offset metadata, then restart the download from byte 0.
- Verify the resource URL/ETag is unchanged between the original request and the resume request (add If-Range).
- Check whether a proxy or CDN is altering range requests; bypass it or disable resume.
- Re-request the range explicitly with the correct Range header matching the stored offset.
Example fix
// before
// resume with stale offset
let resume = stale_offset;
// after
let resume = if head_etag == saved_etag { saved_offset } else { 0 }; Defensive patterns
Strategy: validation
Validate before calling
let head = client.head(url).send()?;
let etag = head.headers().get(header::ETAG).cloned();
let ok = etag.as_deref() == saved_etag.as_deref();
let resume_offset = if ok { saved_offset } else { 0 }; Type guard
fn content_range_matches(header: &str, expected_start: u64) -> bool {
header
.strip_prefix("bytes ")
.and_then(|r| r.split('-').next())
.and_then(|s| s.parse::<u64>().ok())
.map_or(false, |start| start == expected_start)
} Prevention
- Always send If-Range with the ETag/Last-Modified so the server refuses stale resumes.
- Store the offset together with the ETag of the original response.
- Delete partial state when the remote resource changes.
- Fall back to a fresh download when resume validation fails.
When it happens
Trigger: download_file called with a resume offset (Some(resume)) where the server's Content-Range header starts at a byte position different from the requested offset (expected_start != first_byte_pos), e.g. after a 206 Partial Content response reflecting a different range.
Common situations: Resume offset stored in a stale .partial metadata file no longer matches server-side file state; server rewrites or truncates the resource between retries; a proxy returns a different range than requested; resuming against a server that regenerated dynamic content.
Related errors
- Can't parse Content-Range header, can't resume download
- Invalid Content-Range
- Content-Range has wrong end
- Missing Content-Range header
- Bad Content-Range header
AI-assisted analysis of ducaale/xh@2404aceecc (2026-09-13).
Data as JSON: /api/errors/d2eb51ec65b389ea.
Report an issue: GitHub.
Appendix: source
Thrown at src/download.rs:154
.parse()
.context("Can't parse Content-Range complete_length")
})
.transpose()?;
// Note that last_byte_pos must be strictly less than complete_length
// If first_byte_pos == last_byte_pos exactly one byte is sent
if first_byte_pos > last_byte_pos {
return Err(anyhow!("Invalid Content-Range: {:?}", header));
}
if let Some(complete_length) = complete_length {
if last_byte_pos >= complete_length {
return Err(anyhow!("Invalid Content-Range: {:?}", header));
}
if complete_length != last_byte_pos + 1 {
return Err(anyhow!("Content-Range has wrong end: {:?}", header));
}
}
if expected_start != first_byte_pos {
return Err(anyhow!("Content-Range has wrong start: {:?}", header));
}
Ok(last_byte_pos + 1)
}
const BAR_TEMPLATE: &str =
"{spinner:.green} {percent}% [{wide_bar:.cyan/blue}] {bytes} {bytes_per_sec} ETA {eta}";
const UNCOLORED_BAR_TEMPLATE: &str =
"{spinner} {percent}% [{wide_bar}] {bytes} {bytes_per_sec} ETA {eta}";
const SPINNER_TEMPLATE: &str = "{spinner:.green} {bytes} {bytes_per_sec} {wide_msg}";
const UNCOLORED_SPINNER_TEMPLATE: &str = "{spinner} {bytes} {bytes_per_sec} {wide_msg}";
pub fn download_file(
response: Response,
file_name: Option<PathBuf>,
// If we fall back on taking the filename from the URL it has to be the
// original URL, before redirects. That's less surprising and matches
// HTTPie. Hence this argument.
orig_url: &reqwest::Url,View on GitHub (pinned to 2404aceecc)