aaif-goose/goose · error

Failed to download: HTTP {}

Error message

Failed to download: HTTP {}

What it means

The HTTP response status was neither a 2xx nor 206 Partial Content. Only 5xx, 408 and 429 are treated as transient and retried (up to 10 times with backoff); every other status fails immediately, as do transient statuses once retries are exhausted. The status code is embedded in the message text.

Source

Thrown at crates/goose-download-manager/src/lib.rs:513

            let status = response.status();
            if status == reqwest::StatusCode::RANGE_NOT_SATISFIABLE {
                if file_total > 0 && file_bytes == file_total {
                    break;
                }
                *cumulative_bytes = cumulative_bytes.saturating_sub(file_bytes);
                file_bytes = 0;
                let _ = tokio::fs::remove_file(&partial_path).await;
                continue;
            }

            if !status.is_success() && status != reqwest::StatusCode::PARTIAL_CONTENT {
                let is_transient = status.is_server_error()
                    || status == reqwest::StatusCode::REQUEST_TIMEOUT
                    || status == reqwest::StatusCode::TOO_MANY_REQUESTS;

                if !is_transient || retries >= Self::MAX_RETRIES {
                    anyhow::bail!("Failed to download: HTTP {}", status);
                }
                retries += 1;
                let delay = std::cmp::min(
                    Self::RETRY_BASE_DELAY * 2u32.saturating_pow(retries - 1),
                    Self::RETRY_MAX_DELAY,
                );
                info!(model_id = %model_id, retry = retries, http_status = %status, "Retrying download after transient HTTP error");
                Self::cancellable_sleep(delay, downloads, model_id).await?;
                continue;
            }

            if file_bytes > 0 && status == reqwest::StatusCode::OK {
                info!(model_id = %model_id, "Server ignored Range header, restarting file from scratch");
                // Subtract already-counted partial bytes from cumulative
                *cumulative_bytes = cumulative_bytes.saturating_sub(file_bytes);
                file_bytes = 0;
                let _ = tokio::fs::remove_file(&partial_path).await;
            }

View on GitHub (pinned to 3810898a74)

Solutions

  1. Extract the status from the message: 401/403 means authenticate (log in to HuggingFace / supply the bearer token) or use a non-gated mirror
  2. 404 means verify the repo and exact filename on huggingface.co and fix the model spec
  3. 429 means back off well past the Retry-After window before restarting
  4. 5xx means the origin is unhealthy: retry later; the download resumes from the .partial file

Example fix

// before
let result = manager.download_model_sharded_with_bearer_token(id, files, hint, token, None).await;

// after: branch on the embedded HTTP status instead of surfacing a generic error
if let Err(e) = &result {
    if let Some(code) = e.to_string().strip_prefix("Failed to download: HTTP ") {
        match code.trim() {
            "401" | "403" => { /* refresh HF token, then retry */ }
            "404" => { /* fix repo/filename in the spec */ }
            _ => { /* transient or unknown: schedule a later retry */ }
        }
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// confirm reachability and authorization before committing to the transfer
let resp = client.head(url).send().await?;
use reqwest::StatusCode;
match resp.status() {
    s if s.is_success() => {}
    StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN => anyhow::bail!("supply a valid HF token for this gated repo"),
    StatusCode::NOT_FOUND => anyhow::bail!("repo/filename wrong: verify on huggingface.co"),
    s => anyhow::bail!("preflight got HTTP {s}"),
}

Try / catch

if let Some(code) = err.to_string().strip_prefix("Failed to download: HTTP ") {
    match code.trim() {
        "401" | "403" => { /* authenticate or switch to a non-gated mirror */ }
        "404" => { /* fix the repo/filename in the model spec */ }
        "429" => { /* back off past Retry-After, then retry */ }
        s if s.starts_with('5') => { /* origin outage: retry later; resume is automatic */ }
        _ => surface(err),
    }
}

Prevention

When it happens

Trigger: 401/403 when the repo is gated or the bearer token is missing/expired; 404 when the built URL references a nonexistent repo/file or the file was renamed; 400-class errors for malformed requests; 5xx/429 persisting past 10 retries.

Common situations: Downloading gated HF models without huggingface-cli login; a typo in the model spec filename; upstream repo restructure deleting the quantization file; heavy rate limiting during model-release spikes.

Related errors


AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16). Data as JSON: /api/errors/86c093571a1ef78f. Report an issue: GitHub.