NousResearch/hermes-agent · error · RuntimeError

Failed to download {url}: {exc}

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.

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)

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.