fix(auth): a borrowed Codex pool clears its quota cooldown in the root store

With reads now inheriting the global-root pool, a profile hitting a stale
root cooldown probes quota, sees it restored, and calls
clear_codex_pool_quota_cooldowns() — which only ever edited the (empty)
profile store, so the next resolve raised quota_exhausted again forever.
Pick the store the same way agent/credential_pool.py persists borrowed
rows (_profile_owns_pool_provider / _borrowed_single_use_pool_root) and
lock/save against that path. The fallback test now also binds
profile-wins precedence; one new test pins the root write.
This commit is contained in:
kshitijk4poor
2026-09-11 11:08:59 +05:30
committed by kshitij
parent 3767c2eacd
commit 65c9d33b14
2 changed files with 32 additions and 7 deletions

View File

@@ -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

View File

@@ -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