Compare commits

...

1 Commits

Author SHA1 Message Date
Ben
eca2d355f6 fix: skip stale Nous pool entry when agent_key is expired
The credential pool intentionally does NOT refresh Nous entries during
selection — that would trigger network calls in non-runtime contexts
like 'hermes auth list'. But resolve_runtime_provider() was returning
the pool entry's stale agent_key (~30 min TTL) without checking
whether it had expired, causing the inference API to reject requests.

Now, when the pool returns a Nous entry, we check _agent_key_is_usable()
before using it. If the key is expired or missing, pool_api_key is
cleared so the existing fallthrough to resolve_nous_runtime_credentials()
handles the access_token refresh + agent_key mint cycle.
2026-04-10 09:08:51 +10:00

View File

@@ -16,6 +16,7 @@ from hermes_cli.auth import (
DEFAULT_CODEX_BASE_URL,
DEFAULT_QWEN_BASE_URL,
PROVIDER_REGISTRY,
_agent_key_is_usable,
format_auth_error,
resolve_provider,
resolve_nous_runtime_credentials,
@@ -644,6 +645,21 @@ def resolve_runtime_provider(
getattr(entry, "runtime_api_key", None)
or getattr(entry, "access_token", "")
)
# For Nous, the pool entry's runtime_api_key is the agent_key — a
# short-lived inference credential (~30 min TTL). The pool doesn't
# refresh it during selection (that would trigger network calls in
# non-runtime contexts like `hermes auth list`). If the key is
# expired, clear pool_api_key so we fall through to
# resolve_nous_runtime_credentials() which handles refresh + mint.
if provider == "nous" and entry is not None and pool_api_key:
min_ttl = max(60, int(os.getenv("HERMES_NOUS_MIN_KEY_TTL_SECONDS", "1800")))
nous_state = {
"agent_key": getattr(entry, "agent_key", None),
"agent_key_expires_at": getattr(entry, "agent_key_expires_at", None),
}
if not _agent_key_is_usable(nous_state, min_ttl):
logger.debug("Nous pool entry agent_key expired/missing, falling through to runtime resolution")
pool_api_key = ""
if entry is not None and pool_api_key:
return _resolve_runtime_from_pool_entry(
provider=provider,