Skip to content

hf-hub 0.5.0: MissingHeader("Content-Range") on downloads due to ureq v3 gzip interaction #141

@starpit

Description

@starpit

Hey there, I tried updating https://github.com/EricLBuehler/mistral.rs to use hf-hub@0.5.0, but it has failing tests as a result. This seems to come from a change in ureq@3 w.r.t. handling of Accept-Encoding (namely gzip) and Range requests. So this only affects downloading of large files that transit through a CDN.

Here is more complete detail courtesy of Opus 4.6:

Summary

hf-hub 0.5.0 fails with ApiError::MissingHeader("Content-Range") when downloading files from repos that go through HuggingFace's /api/resolve-cache/ redirect path. The metadata() method sends Range: bytes=0-0 but ureq v3 auto-appends Accept-Encoding: gzip, causing the server to ignore the range request and return 200 OK without a Content-Range header.

Not all repos are affected — small files served directly (e.g. gpt2/config.json at 665 bytes) work fine because they return 200 with etag headers inline and skip the redirect/range path entirely. Larger files that go through the resolve-cache redirect are broken.

Reproduction

# Cargo.toml
[dependencies]
hf-hub = { version = "0.5.0", features = ["ureq"] }
use hf_hub::{api::sync::ApiBuilder, Repo, RepoType};

fn main() {
    let api = ApiBuilder::new().with_progress(true).build().unwrap();
    let api = api.repo(Repo::with_revision(
        "EricB/mistralrs_tests".to_string(),
        RepoType::Model,
        "main".to_string(),
    ));

    // Fails with: MissingHeader("Content-Range")
    let path = api.get("tokenizer.json").unwrap();
}

Works on hf-hub 0.4.3, fails on 0.5.0.

Confirmed via debug logs

ureq v2 (hf-hub 0.4.3) — no Accept-Encoding sent, gets 206:

Request  GET https://huggingface.co/.../tokenizer.json  [Range: bytes=0-0]
Response 307 → relative redirect to /api/resolve-cache/...
Request  GET https://huggingface.co/api/resolve-cache/...  [Range: bytes=0-0]
Response 206 Partial Content  [Content-Range: bytes 0-0/1795303]  ✓

ureq v3 (hf-hub 0.5.0)Accept-Encoding: gzip auto-added, gets 200:

Request  GET https://huggingface.co/.../tokenizer.json  [Range: bytes=0-0, Accept-Encoding: gzip]
Response 307 → relative redirect to /api/resolve-cache/...
Request  GET https://huggingface.co/api/resolve-cache/...  [Range: bytes=0-0, Accept-Encoding: gzip]
Response 200 OK  [Content-Encoding: gzip, transfer-encoding: chunked]  ✗ no Content-Range

Root Cause

ureq v2 explicitly suppressed Accept-Encoding when a Range header was present (ureq-2.12.1/src/request.rs:93-111):

fn add_accept_encoding(&mut self) {
    let should_add = !self.headers.iter().map(|h| h.name()).any(|name| {
        name.eq_ignore_ascii_case("accept-encoding") || name.eq_ignore_ascii_case("range")
    });
    // ...
}

ureq v3 dropped this guard (ureq-3.2.0/src/run.rs:316-323). It only checks for an existing Accept-Encoding header, not Range:

let has_header_accept_enc = headers.has_accept_encoding();
if !has_header_accept_enc {
    if let Some(v) = config.accept_encoding().as_str(accepts) {
        call.header(header::ACCEPT_ENCODING, value)?;
    }
}

When the HuggingFace resolve-cache endpoint receives both Range: bytes=0-0 and Accept-Encoding: gzip, it serves the full gzip-compressed body as 200 OK instead of honoring the range request with 206 Partial Content. This is valid per RFC 7233 — servers may ignore Range when they choose to apply a content-coding.

Affected code path

ApiRepo::downloaddownload_with_progressApi::metadata() (fails at src/api/sync.rs:579):

let content_range = response
    .headers()
    .get(CONTENT_RANGE)
    .ok_or(ApiError::MissingHeader(CONTENT_RANGE))?;  // <-- fails here

Suggested Fixes

Option A: Fix in hf-hub (recommended, simplest)

Set accept_encoding to None on both agent builders in Api::build():

use ureq::config::AutoHeaderValue;

let builder = builder()?
    .redirect_auth_headers(RedirectAuthHeaders::SameHost)
    .accept_encoding(AutoHeaderValue::None);
let agent: Agent = builder.build().into();

let no_redirect_agent: Agent = Agent::config_builder()
    .max_redirects(0)
    .accept_encoding(AutoHeaderValue::None)
    .build()
    .into();

hf-hub doesn't need gzip for its API calls — it downloads raw model weight files and uses byte-range requests for resumption. Disabling the auto-header is safe and correct.

Option B: Fix in ureq v3

Restore the ureq v2 behavior: suppress Accept-Encoding auto-header when a Range header is present on the request. This was an intentional design decision in v2 that was lost in the v3 rewrite.

Environment

  • hf-hub 0.5.0 (released 2026-02-19)
  • ureq 3.2.0 (default features include gzip)
  • Tested on macOS / aarch64, likely affects all platforms

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions