From 5d2d25d1ebe7865683d639c5dd3dc8ccf8e68e20 Mon Sep 17 00:00:00 2001 From: webtecnica Date: Sat, 18 Jul 2026 23:39:31 -0300 Subject: [PATCH 1/2] fix: prevent stale agent_version in Docker gateway deployments (#6150) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two changes address /api/settings returning a stale agent_version when WebUI runs in Docker and the Agent sits in another container: 1. _gateway_health_base_url() now falls back through HERMES_API_URL and HERMES_WEBUI_GATEWAY_BASE_URL — matching the chain already used by agent_health.py — so the health probe works with the env vars that Docker compose deployments actually set. 2. /api/settings now calls _detect_agent_version_from_gateway_health() on every request (timeout=2.0) and only falls back to the import-time AGENT_VERSION when the gateway is unreachable. This ensures Settings → System shows the live Agent version instead of whatever was detected at process startup. --- api/routes.py | 14 ++++++++++++-- api/updates.py | 9 ++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/api/routes.py b/api/routes.py index 91d0b73a12..388d475769 100644 --- a/api/routes.py +++ b/api/routes.py @@ -12378,9 +12378,19 @@ def handle_get(handler, parsed) -> bool: # Inject the running version so the UI badge stays in sync with git tags # without any manual release step. try: - from api.updates import AGENT_VERSION, WEBUI_VERSION + from api.updates import ( + AGENT_VERSION, + WEBUI_VERSION, + _detect_agent_version_from_gateway_health, + ) settings["webui_version"] = WEBUI_VERSION - settings["agent_version"] = AGENT_VERSION + # Prefer live gateway health detection so Docker gateway + # deployments always show the actual running Agent version + # even when the import-time AGENT_VERSION is stale (#6150). + settings["agent_version"] = ( + _detect_agent_version_from_gateway_health(timeout=2.0) + or AGENT_VERSION + ) except Exception: pass # Channel-scoped display badge — SEPARATE from webui_version (which is diff --git a/api/updates.py b/api/updates.py index e7258cb31d..d0227138c2 100644 --- a/api/updates.py +++ b/api/updates.py @@ -507,10 +507,17 @@ def _read_agent_source_version(agent_dir: Path) -> str | None: def _gateway_health_base_url() -> str: - """Return the configured/default Hermes Agent gateway base URL.""" + """Return the configured/default Hermes Agent gateway base URL. + + Falls back through the same env-var chain used by agent_health.py + (#6150): GATEWAY_HEALTH_URL → HERMES_GATEWAY_HEALTH_URL → + HERMES_API_URL → HERMES_WEBUI_GATEWAY_BASE_URL → default. + """ raw = ( os.environ.get('GATEWAY_HEALTH_URL') or os.environ.get('HERMES_GATEWAY_HEALTH_URL') + or os.environ.get('HERMES_API_URL') + or os.environ.get('HERMES_WEBUI_GATEWAY_BASE_URL') or 'http://hermes-agent:8642' ).strip() if raw.endswith('/health/detailed'): From 7442ca9f2330bff17519d96d9789e2adf5cc0636 Mon Sep 17 00:00:00 2001 From: webtecnica Date: Mon, 20 Jul 2026 18:12:31 -0300 Subject: [PATCH 2/2] fix(#6150): add TTL cache around gateway agent version detection The raw _detect_agent_version_from_gateway_health was called unconditionally on every /api/settings request, blocking for up to 2 seconds when the gateway was unreachable. Since settings is a hot endpoint (page load, settings open), this was a latency regression for the common case to fix the Docker-display edge case. Fix: - Add _cached_agent_version_from_gateway() with a 30s TTL for positive results and a 5s TTL for negative results (None), so a recovering gateway is picked up promptly - Use the cached wrapper in /api/settings instead of the raw detector - Reduce timeout from 2.0s to 0.75s (already the function's own default) Refs: #6150, #6289 --- api/routes.py | 6 ++++-- api/updates.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/api/routes.py b/api/routes.py index 388d475769..56928bdd64 100644 --- a/api/routes.py +++ b/api/routes.py @@ -12381,14 +12381,16 @@ def handle_get(handler, parsed) -> bool: from api.updates import ( AGENT_VERSION, WEBUI_VERSION, - _detect_agent_version_from_gateway_health, + _cached_agent_version_from_gateway, ) settings["webui_version"] = WEBUI_VERSION # Prefer live gateway health detection so Docker gateway # deployments always show the actual running Agent version # even when the import-time AGENT_VERSION is stale (#6150). + # Uses a TTL cache so unreachable gateways don't block every + # page load (#6289). settings["agent_version"] = ( - _detect_agent_version_from_gateway_health(timeout=2.0) + _cached_agent_version_from_gateway() or AGENT_VERSION ) except Exception: diff --git a/api/updates.py b/api/updates.py index d0227138c2..37df36f741 100644 --- a/api/updates.py +++ b/api/updates.py @@ -563,6 +563,36 @@ def _detect_agent_version_from_gateway_health(timeout: float = 0.75) -> str | No return None +# Cache wrapper so /api/settings doesn't block on every page load when +# the gateway is unreachable (#6150, #6289). Negative results (None) +# are cached with a shorter TTL so a recovering gateway is picked up +# promptly. +_GATEWAY_AGENT_VERSION_CACHE: dict[str, object] = { + "value": None, + "at": 0.0, +} +_GATEWAY_AGENT_VERSION_TTL = 30.0 +_GATEWAY_AGENT_VERSION_NEGATIVE_TTL = 5.0 + + +def _cached_agent_version_from_gateway() -> str | None: + """Return a cached gateway agent version, refreshing at most once per TTL.""" + now = time.monotonic() + cached = _GATEWAY_AGENT_VERSION_CACHE["value"] + cached_at = _GATEWAY_AGENT_VERSION_CACHE["at"] + ttl = ( + _GATEWAY_AGENT_VERSION_NEGATIVE_TTL + if cached is None + else _GATEWAY_AGENT_VERSION_TTL + ) + if cached_at and (now - cached_at) < ttl: + return cached + result = _detect_agent_version_from_gateway_health(timeout=0.75) + _GATEWAY_AGENT_VERSION_CACHE["value"] = result + _GATEWAY_AGENT_VERSION_CACHE["at"] = now + return result + + def _detect_agent_version() -> str: """Detect the running Hermes Agent version for UI display.""" agent_dir = Path(_AGENT_DIR) if _AGENT_DIR is not None else None