NousResearch/hermes-agent · error · RuntimeError
could not reach the management API at {host}:{port} ({exc}).
Error message
could not reach the management API at {host}:{port} ({exc}). If the daemon was started before reload support, run `hermes egress restart` once. What it means
POST to http://<host>:<port>/v1/reload failed at the transport layer (URLError/OSError): connection refused, timeout, or reset. The comment in the code names the classic cause — a daemon started from a pre-management config is alive (pid checks pass) but has no listener on the management port. The message tells the user to do the one-time restart.
Source
Thrown at agent/proxy_sources/iron_proxy.py:988
pass
if exc.code == 422:
raise RuntimeError(
f"iron-proxy rejected the new config (validation failed; "
f"the running ruleset is unchanged): {body}"
) from exc
if exc.code == 401:
raise RuntimeError(
"management API rejected our key (401). The running "
"daemon was started with a different management.token — "
"run `hermes egress restart`."
) from exc
raise RuntimeError(
f"management reload failed (HTTP {exc.code}): {body}"
) from exc
except (urllib.error.URLError, OSError) as exc:
# A daemon started from a pre-management config is alive but has
# no listener on the management port.
raise RuntimeError(
f"could not reach the management API at {host}:{port} ({exc}). "
"If the daemon was started before reload support, run "
"`hermes egress restart` once."
) from exc
def _default_http_listen(tunnel_port: int) -> List[str]:
"""Build the single host:port bind the proxy should listen on.
iron-proxy v0.39 supports exactly ONE ``proxy.http_listen`` bind per
daemon process, so this returns a one-element list and the choice of
host matters:
* **Linux:** bind the docker bridge gateway (``172.17.0.1`` by
default). Sandboxes reach the proxy via
``host.docker.internal:host-gateway``, which Docker resolves to
exactly this bridge gateway IP on Linux — a loopback-only bind is
unreachable from inside containers there. The bridge IP is stillView on GitHub (pinned to c896c09c42)
Solutions
- Run `hermes egress restart` once — this both upgrades the daemon to a management-capable config and re-binds the listener.
- If it persists, check the pid actually belongs to iron-proxy (ps -p <pid>) and that the management host:port in proxy.yaml is loopback-reachable (ss -ltnp).
- Verify daemon health from iron-proxy.log for hangs.
Defensive patterns
Strategy: try-catch
Validate before calling
import socket
def mgmt_listener_reachable(host: str, port: int, timeout: float = 2.0) -> bool:
try:
with socket.create_connection((host, port), timeout=timeout):
return True
except OSError:
return False Try / catch
try:
reload_proxy()
except RuntimeError as e:
if "could not reach the management API" in str(e):
# pre-management daemon or dead listener — one restart fixes both
raise Prevention
- Do the one-time `hermes egress restart` after upgrading to a management-capable version.
- Health-check the management port (not just the pid) before reload-driven automation.
When it happens
Trigger: reload_proxy() when nothing accepts the connection on the management host:port — daemon predates management support, daemon hung/dead but pid reused by another process (so _pid_alive passes), or the management listener binds a different address than the config says.
Common situations: Post-upgrade first reload on a long-running daemon; host reboot where the pid was recycled; firewall/nftables rules on loopback exotic setups blocking the port.
Related errors
- The generated proxy.yaml has no management listener (written
- Failed to download {url}: {exc}
- iron-proxy is not running — nothing to reload. Run `hermes
- management.token is missing — re-run `hermes egress setup`,
- management API returned unexpected status {resp.status}
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/a4f6c4b971dff275.
Report an issue: GitHub.