diff --git a/hermes_cli/auth_codex.py b/hermes_cli/auth_codex.py index 3b767bd3f9..ad3b833dba 100644 --- a/hermes_cli/auth_codex.py +++ b/hermes_cli/auth_codex.py @@ -579,11 +579,15 @@ def clear_codex_pool_quota_cooldowns(access_token: Optional[str] = None) -> int: rate-limited entry does (a redeemed banked reset restores the whole account; a still-exhausted entry just re-freezes with fresh metadata on its next 429). """ + from agent.credential_pool import _borrowed_single_use_pool_root, _profile_owns_pool_provider from hermes_cli.auth import _auth_store_lock, _load_auth_store, _save_auth_store cleared = 0 try: - with _auth_store_lock(): - auth_store = _load_auth_store() + # A profile borrowing the global-root pool must clear the cooldown where the rows live, + # or the restored quota stays frozen behind the root's stale ``last_error_reset_at``. + target = None if _profile_owns_pool_provider("openai-codex") else _borrowed_single_use_pool_root() + with _auth_store_lock(target_path=target): + auth_store = _load_auth_store(target) entries = _pool_entries(auth_store, "openai-codex") if entries is None: return 0 @@ -594,7 +598,7 @@ def clear_codex_pool_quota_cooldowns(access_token: Optional[str] = None) -> int: _clear_pool_entry_status(entry) cleared += 1 if cleared: - _save_auth_store(auth_store) + _save_auth_store(auth_store, target_path=target) except Exception: logger.debug("Failed to clear Codex pool quota cooldowns", exc_info=True) return cleared diff --git a/tests/hermes_cli/test_auth_profile_fallback.py b/tests/hermes_cli/test_auth_profile_fallback.py index 394113261b..73a8bbcf2a 100644 --- a/tests/hermes_cli/test_auth_profile_fallback.py +++ b/tests/hermes_cli/test_auth_profile_fallback.py @@ -149,10 +149,6 @@ def test_provider_auth_state_returns_none_when_neither_has_it(profile_env): # --------------------------------------------------------------------------- - - - - def test_codex_runtime_uses_global_pool_when_profile_singleton_is_empty(profile_env): """Stale empty profile Codex state must not block the global credential pool.""" from hermes_cli.auth import resolve_codex_runtime_credentials @@ -183,6 +179,31 @@ def test_codex_runtime_uses_global_pool_when_profile_singleton_is_empty(profile_ assert creds["source"] == "credential_pool" assert creds["api_key"] == "global-codex-access-token" + # Profile rows shadow the root the moment they exist (read_credential_pool precedence). + _write(profile_env["profile"] / "auth.json", _make_auth_store(pool={ + "openai-codex": [{"id": "prof", "auth_type": "oauth", "priority": 0, + "access_token": "profile-codex-access-token", "refresh_token": "r"}], + })) + assert resolve_codex_runtime_credentials(refresh_if_expiring=False)["api_key"] == "profile-codex-access-token" + + +def test_codex_cooldown_clear_writes_to_the_store_that_owns_the_borrowed_pool(profile_env): + """A restored quota must unfreeze the ROOT row a profile borrows; clearing the (empty) + profile store would leave every later resolve stuck on the stale cooldown.""" + from hermes_cli.auth_codex import clear_codex_pool_quota_cooldowns + + _write(profile_env["global"] / "auth.json", _make_auth_store(pool={ + "openai-codex": [{"id": "glob", "auth_type": "oauth", "priority": 0, + "access_token": "global-codex-access-token", "refresh_token": "r", + "last_status": "exhausted", "last_error_reason": "rate_limit", + "last_error_reset_at": 4_102_444_800}], + })) + _write(profile_env["profile"] / "auth.json", _make_auth_store(pool={"openai-codex": []})) + + assert clear_codex_pool_quota_cooldowns() == 1 + root_rows = json.loads((profile_env["global"] / "auth.json").read_text())["credential_pool"]["openai-codex"] + assert root_rows[0].get("last_error_reset_at") is None + # --------------------------------------------------------------------------- # Classic mode — no fallback path should ever trigger