diff --git a/gateway/status.py b/gateway/status.py index b925571c96d..c13752af171 100644 --- a/gateway/status.py +++ b/gateway/status.py @@ -595,7 +595,7 @@ def write_runtime_status( if restart_requested is not _UNSET: payload["restart_requested"] = bool(restart_requested) if active_agents is not _UNSET: - payload["active_agents"] = max(0, int(active_agents)) + payload["active_agents"] = parse_active_agents(active_agents) if served_profiles is not _UNSET: # Profiles this gateway multiplexes (multi-profile mode). Absent/empty # for a single-profile gateway. Lets `hermes status` show per-profile @@ -624,11 +624,11 @@ def read_runtime_status() -> Optional[dict[str, Any]]: def parse_active_agents(raw: Any) -> int: """Coerce a persisted ``active_agents`` value to a clamped non-negative int. - The status file is written atomically but can still hold an - absent/None/garbage ``active_agents`` after a partial write or a manual - edit. Both HTTP surfaces (``/api/status`` and ``/health/detailed``) read it - through this single helper so the field they expose is consistent and never - negative. Mirrors the write-side clamp in ``write_runtime_status``. + The shared coercion for the in-flight gateway-turn count. Used on the WRITE + side (``write_runtime_status``) and by both HTTP read surfaces + (``/api/status`` and ``/health/detailed``) so the count is clamped to a + single contract — never negative, never raising on a manually-edited or + otherwise non-numeric value (degrades to ``0``). """ try: return max(0, int(raw)) diff --git a/hermes_cli/gateway.py b/hermes_cli/gateway.py index cf65af98c40..34f7b96a984 100644 --- a/hermes_cli/gateway.py +++ b/hermes_cli/gateway.py @@ -4573,7 +4573,9 @@ def _runtime_health_lines() -> list[str]: lines.append(f"⚠ Last startup issue: {exit_reason}") elif gateway_state == "draining": action = "restart" if restart_requested else "shutdown" - count = int(active_agents or 0) + from gateway.status import parse_active_agents + + count = parse_active_agents(active_agents) lines.append(f"⏳ Gateway draining for {action} ({count} active agent(s))") elif gateway_state == "stopped" and exit_reason: lines.append(f"⚠ Last shutdown reason: {exit_reason}") diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index 8e1e0e72124..74ea8182533 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -1844,7 +1844,7 @@ async def get_status(profile: Optional[str] = None): # liveness via the single shared contract in gateway.status. Liveness # keys off gateway_running (a live PID/health probe), NEVER # gateway_updated_at — a healthy idle gateway never advances that. - active_agents = parse_active_agents(runtime.get("active_agents", 0)) if runtime else 0 + active_agents = parse_active_agents((runtime or {}).get("active_agents", 0)) gateway_busy = derive_gateway_busy( gateway_running=gateway_running, gateway_state=gateway_state, @@ -1862,8 +1862,12 @@ async def get_status(profile: Optional[str] = None): from hermes_cli.gateway import _get_restart_drain_timeout restart_drain_timeout = _get_restart_drain_timeout() - except Exception: - restart_drain_timeout = None + except ImportError: + # Resolver moved/renamed — fall back to the real default so the + # field stays a numeric poll-deadline hint, never None. + from gateway.restart import DEFAULT_GATEWAY_RESTART_DRAIN_TIMEOUT + + restart_drain_timeout = DEFAULT_GATEWAY_RESTART_DRAIN_TIMEOUT # Dashboard auth gate (Phase 7): surface whether the gate is engaged # and which providers are registered so ``hermes status`` and the