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)
.awaitView on GitHub (pinned to c896c09c42)
Solutions
- Retry — transient 5xx/rate-limit statuses usually clear; the branch-pin path already avoids poisoned caches on retry.
- curl -I the exact URL from the error message to see the status and whether a proxy is intercepting.
- If the branch was renamed/deleted, rebuild or re-run with a pin to an existing commit SHA.
- 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
- Pin releases to commit SHAs so a renamed/deleted branch can't 404 an old installer.
- Verify network egress to the script host (and proxy config) before running the installer on locked-down machines.
- Keep the branch-pin refresh behavior intact so retries never serve a poisoned cached script.
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
- Could not fetch image: ${response.status}
- gh pr create failed (is gh installed and authenticated?)
- Gateway did not return a WS ticket.
- Reached the gateway over HTTP, but the live WebSocket (/api/
- Extension "${id}" was not found on the Marketplace.
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/fa03069a8e5ba941.
Report an issue: GitHub.