NousResearch/hermes-agent · error · anyhow::Error

Failed to download {}: HTTP {} from {}

Error message

Failed to download {}: HTTP {} from {}

What it means

Raised by the network fallback in install-script resolution: the HTTP GET for install-main.ps1 (or the kind-specific file) returned a non-2xx status. The client already applied a 10s connect / 60s overall timeout and a hermes-setup User-Agent; only the response status check fails here — transport errors surface as the `GET {url}` context error instead.

Source

Thrown at apps/bootstrap-installer/src-tauri/src/install_script.rs:358

            .extension()
            .and_then(|s| s.to_str())
            .unwrap_or("tmp");
        format!("{ext}.tmp")
    });

    let response = reqwest::Client::builder()
        .connect_timeout(std::time::Duration::from_secs(10))
        .timeout(std::time::Duration::from_secs(60))
        .build()
        .context("building download client")?
        .get(&url)
        .header("User-Agent", "hermes-setup/0.0.1")
        .send()
        .await
        .with_context(|| format!("GET {url}"))?;

    if !response.status().is_success() {
        return Err(anyhow!(
            "Failed to download {}: HTTP {} from {}",
            kind.filename(),
            response.status(),
            url
        ));
    }

    let bytes = response
        .bytes()
        .await
        .with_context(|| format!("reading body of {url}"))?;
    let bytes = prepare_cached_script_bytes(kind, &bytes);

    let mut file = tokio::fs::File::create(&tmp_path)
        .await
        .with_context(|| format!("creating temp file {}", tmp_path.display()))?;
    file.write_all(&bytes)
        .await

View on GitHub (pinned to c896c09c42)

Solutions

  1. Retry — transient 5xx/rate-limit statuses usually clear; the branch-pin path already avoids poisoned caches on retry.
  2. curl -I the exact URL from the error message to see the status and whether a proxy is intercepting.
  3. If the branch was renamed/deleted, rebuild or re-run with a pin to an existing commit SHA.
  4. Whitelist the script host in proxy/antivirus config, or configure the proxy env for the installer process.
Defensive patterns

Strategy: retry

Try / catch

// Retry with backoff on transient statuses; branch pins are always re-fetched so a poisoned cache can't stick.
async fn fetch_script_with_retry(url: &str, kind: Kind) -> Result<bytes::Bytes> {
    let mut attempt = 0;
    loop {
        attempt += 1;
        match download(url, kind).await {
            Ok(b) => return Ok(b),
            Err(e) if attempt < 3 && is_retryable(&e) => {
                tokio::time::sleep(std::time::Duration::from_secs(2u64.pow(attempt))).await;
            }
            Err(e) => return Err(e),
        }
    }
}

fn is_retryable(e: &anyhow::Error) -> bool {
    let s = format!("{e:#}");
    s.contains("HTTP 5") || s.contains("HTTP 429") || s.contains("timed out")
}

Prevention

When it happens

Trigger: Fetching the script URL when the pinned commit/branch does not exist upstream (404), GitHub is rate-limiting or serving a 5xx, a proxy in the middle returns 407/502, or the branch was renamed after the installer was built (410/404).

Common situations: Corporate proxies intercepting raw.githubusercontent.com; GitHub API rate limits on CI runners; using a stale installer binary pinned to a branch that was since deleted; typosquat URL rewrite by DNS-level filtering.

Related errors


AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14). Data as JSON: /api/errors/fa03069a8e5ba941. Report an issue: GitHub.