fix(hermes): preserve proven Claude authentication
This commit is contained in:
parent
0682b2b813
commit
6b378fd410
@ -67,11 +67,6 @@ _auth_probe_value: dict[str, Any] = {}
|
||||
HEALTH_POLL_SECONDS: Final = max(
|
||||
30, int(os.environ.get("HERMES_CLAUDE_HEALTH_POLL_SECONDS", "60"))
|
||||
)
|
||||
AUTH_PROBE_GRACE_SECONDS: Final = max(
|
||||
60, int(os.environ.get("HERMES_CLAUDE_AUTH_PROBE_GRACE_SECONDS", "300"))
|
||||
)
|
||||
|
||||
|
||||
def _read_secret(file_env_name: str) -> str:
|
||||
"""Read a secret only from its runtime-mounted file."""
|
||||
path = os.environ.get(file_env_name, "").strip()
|
||||
@ -296,32 +291,28 @@ def _subscription_health(force: bool = False) -> dict[str, Any]:
|
||||
)
|
||||
api_provider = raw.get("apiProvider") if isinstance(raw, dict) else None
|
||||
auth_method = raw.get("authMethod") if isinstance(raw, dict) else None
|
||||
# A concurrent inference can make Claude's secondary auth-status
|
||||
# process time out or return no JSON. A real, recent inference is
|
||||
# stronger evidence than that inconclusive probe. Preserve the last
|
||||
# proven-good auth state briefly, but never mask an explicit logged-out
|
||||
# response from the CLI.
|
||||
recent_success = False
|
||||
try:
|
||||
last_success = datetime.fromisoformat(
|
||||
str(previous.get("last_success_at") or "").replace("Z", "+00:00")
|
||||
# Cold starts and concurrent inference can make Claude's secondary
|
||||
# auth-status process time out or return no JSON. Once this broker has
|
||||
# proven first-party access, only an explicit logged-out response or an
|
||||
# authoritative inference auth failure may revoke that evidence.
|
||||
# Otherwise periodic probe timeouts would repeatedly eject a working
|
||||
# subscription from routing.
|
||||
known_authenticated = bool(
|
||||
(
|
||||
previous.get("authenticated") is True
|
||||
and previous.get("api_provider") == "firstParty"
|
||||
)
|
||||
if last_success.tzinfo is None:
|
||||
last_success = last_success.replace(tzinfo=timezone.utc)
|
||||
age = (checked_time - last_success.astimezone(timezone.utc)).total_seconds()
|
||||
recent_success = 0 <= age <= AUTH_PROBE_GRACE_SECONDS
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
or previous.get("last_authenticated_at")
|
||||
or previous.get("last_success_at")
|
||||
)
|
||||
probe_stale = bool(
|
||||
not probe_conclusive
|
||||
and recent_success
|
||||
and previous.get("authenticated") is True
|
||||
and previous.get("api_provider") == "firstParty"
|
||||
and known_authenticated
|
||||
)
|
||||
if probe_stale:
|
||||
authenticated = True
|
||||
api_provider = previous.get("api_provider")
|
||||
auth_method = previous.get("auth_method")
|
||||
api_provider = previous.get("api_provider") or "firstParty"
|
||||
auth_method = previous.get("auth_method") or "oauth_token"
|
||||
value = {
|
||||
"transport": "claude-code-cli-subscription",
|
||||
"state": "available" if authenticated else "unavailable",
|
||||
@ -332,8 +323,16 @@ def _subscription_health(force: bool = False) -> dict[str, Any]:
|
||||
"auth_probe_stale": probe_stale,
|
||||
"subscription_type": raw.get("subscriptionType")
|
||||
if isinstance(raw, dict)
|
||||
else None,
|
||||
else previous.get("subscription_type"),
|
||||
}
|
||||
if authenticated:
|
||||
value["last_authenticated_at"] = (
|
||||
checked_at
|
||||
if probe_conclusive
|
||||
else previous.get("last_authenticated_at")
|
||||
or previous.get("last_success_at")
|
||||
or checked_at
|
||||
)
|
||||
# Readiness probes must not erase the most recently observed native
|
||||
# usage window or successful route metadata. They only refresh auth.
|
||||
for key in (
|
||||
@ -447,6 +446,11 @@ def _invoke(payload: dict[str, Any]) -> tuple[dict[str, Any], dict[str, int], st
|
||||
health.update(
|
||||
{
|
||||
"state": "available",
|
||||
"authenticated": True,
|
||||
"api_provider": "firstParty",
|
||||
"auth_method": "oauth_token",
|
||||
"auth_probe_stale": False,
|
||||
"last_authenticated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"last_success_at": datetime.now(timezone.utc).isoformat(),
|
||||
"latency_ms": int((time.monotonic() - started) * 1000),
|
||||
"model": actual_model,
|
||||
|
||||
@ -154,6 +154,36 @@ def test_transient_auth_probe_keeps_recent_proven_access(monkeypatch, tmp_path)
|
||||
assert health["auth_probe_stale"] is True
|
||||
|
||||
|
||||
def test_transient_auth_probe_keeps_older_proven_access(monkeypatch, tmp_path) -> None:
|
||||
module = _module(monkeypatch)
|
||||
module.HEALTH_PATH = tmp_path / "claude.json"
|
||||
module.HEALTH_PATH.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"authenticated": False,
|
||||
"last_success_at": "2026-01-01T00:00:00+00:00",
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
module.subprocess,
|
||||
"run",
|
||||
lambda *_args, **_kwargs: SimpleNamespace(
|
||||
returncode=1, stdout="", stderr="transient timeout"
|
||||
),
|
||||
)
|
||||
|
||||
health = module._subscription_health(force=True)
|
||||
|
||||
assert health["authenticated"] is True
|
||||
assert health["state"] == "available"
|
||||
assert health["api_provider"] == "firstParty"
|
||||
assert health["auth_method"] == "oauth_token"
|
||||
assert health["auth_probe_stale"] is True
|
||||
assert health["last_authenticated_at"] == "2026-01-01T00:00:00+00:00"
|
||||
|
||||
|
||||
def test_explicit_logged_out_probe_clears_recent_access(monkeypatch, tmp_path) -> None:
|
||||
module = _module(monkeypatch)
|
||||
module.HEALTH_PATH = tmp_path / "claude.json"
|
||||
@ -183,3 +213,4 @@ def test_explicit_logged_out_probe_clears_recent_access(monkeypatch, tmp_path) -
|
||||
assert health["authenticated"] is False
|
||||
assert health["state"] == "unavailable"
|
||||
assert health["auth_probe_stale"] is False
|
||||
assert "last_authenticated_at" not in health
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user