NousResearch/hermes-agent · error · RuntimeError

Failed to download

Error message

Failed to download {url}: {exc}

What it means

The release asset download uses urllib with a plain Request; any urllib.error.URLError (DNS failure, connection refused, TLS error, HTTP 404 surfaced as URLError) is wrapped into this RuntimeError with the failing URL. It fires during the lazy auto-install path, before any checksum or extraction work.

Solutions

  1. Confirm network reachability of the URL in the message (curl -I) and fix DNS/firewall/proxy env.
  2. If the release tag/asset 404s, update to a Hermes version whose _IRON_PROXY_VERSION matches an existing release, or manually install the binary.
  3. Pre-install the binary manually (or with `hermes egress install` on a connected machine) so runtime lazy-install is never triggered.
Defensive patterns

Strategy: retry

Validate before calling

import socket, urllib.request

def release_host_reachable(url: str) -> bool:
    try:
        urllib.request.urlopen(urllib.request.Request(url, method="HEAD"), timeout=10)
        return True
    except Exception:
        return False

Try / catch

for attempt in range(2):
    try:
        find_iron_proxy(install_if_missing=True)
        break
    except RuntimeError as e:
        if "Failed to download" in str(e) and attempt == 0:
            continue  # one retry for transient network
        raise

Prevention

When it happens

Trigger: find_iron_proxy(install_if_missing=True) / `hermes egress install` when the machine is offline, DNS for the release host fails, a firewall blocks egress, the tagged release/asset was removed upstream, or ambient HTTP(S)_PROXY env points at a dead proxy (the download honors ambient proxy env).

Common situations: Egress-filtered containers and CI sandboxes; stale _IRON_PROXY_VERSION pin pointing at a release whose assets were re-uploaded/renamed; broken proxy env inherited from the operator's shell.

Related errors


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

Appendix: source

Thrown at agent/proxy_sources/iron_proxy.py:556

    # Invalidate the version cache so a freshly-installed binary
    # re-probes ``--version`` on the next ``get_status()`` call instead
    # of returning the pre-upgrade string.  Long-lived processes that
    # bump the pinned version via ``force=True`` need this.
    _VERSION_CACHE.pop(str(target), None)

    logger.info("Installed iron-proxy %s at %s", _IRON_PROXY_VERSION, target)
    return target


def _http_download(url: str, dest: Path) -> None:
    req = urllib.request.Request(url, headers={"User-Agent": "hermes-agent"})
    try:
        with urllib.request.urlopen(req, timeout=_DOWNLOAD_TIMEOUT) as resp:  # noqa: S310
            with open(dest, "wb") as f:
                shutil.copyfileobj(resp, f)
    except urllib.error.URLError as exc:
        raise RuntimeError(f"Failed to download {url}: {exc}") from exc


def _verify_checksums_signature(tmp: Path, checksum_path: Path) -> bool:
    """Best-effort GPG verification of ``checksums.txt`` (maxpetrusenko P1).

    Downloads the detached signature (``checksums.txt.asc``) and the release
    signing key (``public-key.asc``), imports the key into an ephemeral
    keyring, and verifies the signature over ``checksum_path``.

    Returns True when the signature is verified. Returns False (with a warning)
    when verification is unavailable — ``gpg`` not installed, or the signature /
    public-key assets are missing from the release. Raises RuntimeError ONLY
    when verification actively FAILS (a present-but-bad signature), which is a
    tamper signal we must not ignore.

    Rationale for graceful degradation on "unavailable": the SHA-256 check
    against ``checksums.txt`` remains in force regardless, and many install
    hosts (CI, minimal containers) won't have gpg. We harden when we can and

View on GitHub (pinned to c896c09c42)