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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user