Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion agent/account_usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,7 @@ def redeem_codex_reset_credit(
try:
from hermes_cli.auth import clear_codex_pool_quota_cooldowns

clear_codex_pool_quota_cooldowns()
clear_codex_pool_quota_cooldowns(api_key)
except Exception:
logger.debug(
"Failed to clear Codex pool cooldowns after reset redemption",
Expand Down
10 changes: 5 additions & 5 deletions hermes_cli/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -3870,7 +3870,7 @@ def resolve_codex_runtime_credentials(
logger.info(
"Codex quota restored upstream — clearing stale pool cooldown(s)."
)
clear_codex_pool_quota_cooldowns()
clear_codex_pool_quota_cooldowns(stale_token)
pool_token = _pool_codex_access_token()
if pool_token:
base_url = (
Expand Down Expand Up @@ -4091,10 +4091,10 @@ def clear_codex_pool_quota_cooldowns(access_token: Optional[str] = None) -> int:
``exhausted`` entries whose error metadata is 429/quota-shaped — DEAD
(terminal auth) entries and non-rate-limit failures are untouched.

When *access_token* is given, only the matching entry is cleared;
otherwise every rate-limited entry clears (a redeemed banked reset
restores the whole account, and any entry that is genuinely still
exhausted just re-freezes with fresh metadata on its next 429).
When *access_token* is given, only the matching entry is cleared. Callers
handling an account-specific live probe or reset redemption must pass it
so one recovered account cannot reactivate unrelated exhausted accounts.
Omitting it is reserved for an explicit whole-pool administrative reset.

Returns the number of entries cleared.
"""
Expand Down
58 changes: 48 additions & 10 deletions tests/hermes_cli/test_auth_codex_quota_probe.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,9 +326,26 @@ def _pool_only_rate_limited_store(now=None):
def test_resolver_recovers_when_probe_confirms_reset(tmp_path, monkeypatch):
"""The screenshot bug: pool-only cooldown raises `quota exhausted (429);
retry after Ns` even though the upstream window already reset. A positive
probe must clear the cooldown and return the pool credential."""
probe must clear only that credential's cooldown and return it."""
hermes_home = tmp_path / "hermes"
_write_auth_store(hermes_home, _pool_only_rate_limited_store())
store = _pool_only_rate_limited_store()
store["credential_pool"]["openai-codex"].append(
{
"id": "cred-still-exhausted",
"label": "other-account",
"auth_type": "oauth",
"priority": 1,
"source": "manual:device_code",
"access_token": "tok-other",
"last_status": "exhausted",
"last_status_at": time.time(),
"last_error_code": 429,
"last_error_reason": "usage_limit_reached",
"last_error_message": "The usage limit has been reached",
"last_error_reset_at": time.time() + 4 * 24 * 3600,
}
)
_write_auth_store(hermes_home, store)
monkeypatch.setenv("HERMES_HOME", str(hermes_home))

monkeypatch.setattr(
Expand All @@ -340,9 +357,11 @@ def test_resolver_recovers_when_probe_confirms_reset(tmp_path, monkeypatch):
assert resolved["source"] == "credential_pool"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current main retains this recovery test but has pruned and renumbered it to lines 221-240. Port this two-account assertion into that surviving test during salvage so the token-scoping regression remains covered.


store = json.loads((hermes_home / "auth.json").read_text())
entry = store["credential_pool"]["openai-codex"][0]
assert entry["last_status"] is None
assert entry["last_error_reset_at"] is None
entries = {e["id"]: e for e in store["credential_pool"]["openai-codex"]}
assert entries["cred-quota"]["last_status"] is None
assert entries["cred-quota"]["last_error_reset_at"] is None
assert entries["cred-still-exhausted"]["last_status"] == "exhausted"
assert entries["cred-still-exhausted"]["last_error_reset_at"] is not None


def test_resolver_keeps_cooldown_when_probe_negative(tmp_path, monkeypatch):
Expand Down Expand Up @@ -462,7 +481,24 @@ def _spy(token, **kw):

def test_redeem_reset_clears_pool_cooldowns(tmp_path, monkeypatch):
hermes_home = tmp_path / "hermes"
_write_auth_store(hermes_home, _pool_only_rate_limited_store())
store = _pool_only_rate_limited_store()
store["credential_pool"]["openai-codex"].append(
{
"id": "cred-still-exhausted",
"label": "other-account",
"auth_type": "oauth",
"priority": 1,
"source": "manual:device_code",
"access_token": "tok-other",
"last_status": "exhausted",
"last_status_at": time.time(),
"last_error_code": 429,
"last_error_reason": "usage_limit_reached",
"last_error_message": "The usage limit has been reached",
"last_error_reset_at": time.time() + 4 * 24 * 3600,
}
)
_write_auth_store(hermes_home, store)
monkeypatch.setenv("HERMES_HOME", str(hermes_home))

from agent import account_usage
Expand Down Expand Up @@ -498,11 +534,13 @@ def post(self, url, headers=None, json=None):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current main has no remaining reset-redemption test in this file after its test-pruning change. Recreate this two-account reset regression in a current test location when salvaging the production call change.

result = account_usage.redeem_codex_reset_credit(
base_url="https://chatgpt.com/backend-api/codex",
api_key="live-agent-token",
api_key="tok-quota",
)
assert result.redeemed

store = json.loads((hermes_home / "auth.json").read_text())
entry = store["credential_pool"]["openai-codex"][0]
assert entry["last_status"] is None
assert entry["last_error_reset_at"] is None
entries = {e["id"]: e for e in store["credential_pool"]["openai-codex"]}
assert entries["cred-quota"]["last_status"] is None
assert entries["cred-quota"]["last_error_reset_at"] is None
assert entries["cred-still-exhausted"]["last_status"] == "exhausted"
assert entries["cred-still-exhausted"]["last_error_reset_at"] is not None