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

  1. Delete the partial download file and its resume offset metadata, then restart the download from byte 0.
  2. Verify the resource URL/ETag is unchanged between the original request and the resume request (add If-Range).
  3. Check whether a proxy or CDN is altering range requests; bypass it or disable resume.
  4. 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

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


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)