From 985b034a514dee95a679bc70066f29b3515ee853 Mon Sep 17 00:00:00 2001 From: briandevans <252620095+briandevans@users.noreply.github.com> Date: Wed, 12 Aug 2026 20:40:10 -0700 Subject: [PATCH] fix(proxy): keep the health endpoint off the auth-store lock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `handle_health` called `adapter.is_authenticated()` inline from an `async def`. `UpstreamAdapter.is_authenticated` is documented as "Should be cheap — no network calls. Used by `proxy start` for a clear up-front error before binding a port." (`adapters/base.py`), and that is true of the `proxy start` preflight, which runs in a plain synchronous CLI function. It is not true on the event loop: `NousPortalAdapter.is_authenticated` goes through `_read_state()`, which takes `_auth_store_lock()` — the same cross-process lock with a 15s timeout as credential resolution — and `XAIGrokAdapter` reads its key pool off disk. `/health` is precisely what a supervisor, systemd unit, container healthcheck or load balancer polls, on a fixed interval, so it is the endpoint least able to afford a lock wait; and a wait here freezes every concurrent proxied stream, not just the healthcheck. Offload it with `asyncio.to_thread`. The response body is byte-identical; only the scheduling changes. --- hermes_cli/proxy/server.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/hermes_cli/proxy/server.py b/hermes_cli/proxy/server.py index 2dff6f95b5..9645cbb2a0 100644 --- a/hermes_cli/proxy/server.py +++ b/hermes_cli/proxy/server.py @@ -112,11 +112,17 @@ def create_app(adapter: UpstreamAdapter) -> "web.Application": app[_adapter_key] = adapter async def handle_health(request: "web.Request") -> "web.Response": + # ``is_authenticated`` is documented as cheap (see UpstreamAdapter), + # but both shipped adapters read auth state off disk, and the Nous one + # does it under ``_auth_store_lock()`` — 15s cross-process. Offload it + # so a healthcheck poll can never freeze the loop behind a lock held by + # a concurrent ``hermes auth`` command. + authenticated = await asyncio.to_thread(adapter.is_authenticated) return web.json_response( { "status": "ok", "upstream": adapter.display_name, - "authenticated": adapter.is_authenticated(), + "authenticated": authenticated, } )