From 5cf4fac2aae0fb73ebe8760cd099924e8b4b996d Mon Sep 17 00:00:00 2001 From: Cherif Yaya Date: Thu, 9 Apr 2026 00:04:30 -0700 Subject: [PATCH] fix: restore codex fallback auth-store lookup --- agent/auxiliary_client.py | 22 ++++++++++++++----- tests/agent/test_auxiliary_client.py | 32 ++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/agent/auxiliary_client.py b/agent/auxiliary_client.py index f743a64e..27c67c10 100644 --- a/agent/auxiliary_client.py +++ b/agent/auxiliary_client.py @@ -629,11 +629,19 @@ def _nous_base_url() -> str: def _read_codex_access_token() -> Optional[str]: - """Read a valid, non-expired Codex OAuth access token from Hermes auth store.""" + """Read a valid, non-expired Codex OAuth access token from Hermes auth store. + + If a credential pool exists but currently has no selectable runtime entry + (for example all pool slots are marked exhausted), fall back to the + profile's auth.json token instead of hard-failing. This keeps explicit + fallback-to-Codex working when the pool state is stale but the stored OAuth + token is still valid. + """ pool_present, entry = _select_pool_entry("openai-codex") if pool_present: token = _pool_runtime_api_key(entry) - return token or None + if token: + return token try: from hermes_cli.auth import _read_codex_tokens @@ -894,9 +902,13 @@ def _try_codex() -> Tuple[Optional[Any], Optional[str]]: pool_present, entry = _select_pool_entry("openai-codex") if pool_present: codex_token = _pool_runtime_api_key(entry) - if not codex_token: - return None, None - base_url = _pool_runtime_base_url(entry, _CODEX_AUX_BASE_URL) or _CODEX_AUX_BASE_URL + if codex_token: + base_url = _pool_runtime_base_url(entry, _CODEX_AUX_BASE_URL) or _CODEX_AUX_BASE_URL + else: + codex_token = _read_codex_access_token() + if not codex_token: + return None, None + base_url = _CODEX_AUX_BASE_URL else: codex_token = _read_codex_access_token() if not codex_token: diff --git a/tests/agent/test_auxiliary_client.py b/tests/agent/test_auxiliary_client.py index dd02ad23..37233789 100644 --- a/tests/agent/test_auxiliary_client.py +++ b/tests/agent/test_auxiliary_client.py @@ -77,6 +77,20 @@ class TestReadCodexAccessToken: result = _read_codex_access_token() assert result == "tok-123" + def test_pool_without_selected_entry_falls_back_to_auth_store(self, tmp_path, monkeypatch): + hermes_home = tmp_path / "hermes" + hermes_home.mkdir(parents=True, exist_ok=True) + monkeypatch.setenv("HERMES_HOME", str(hermes_home)) + + valid_jwt = "eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjk5OTk5OTk5OTl9.sig" + with patch("agent.auxiliary_client._select_pool_entry", return_value=(True, None)), \ + patch("hermes_cli.auth._read_codex_tokens", return_value={ + "tokens": {"access_token": valid_jwt, "refresh_token": "refresh"} + }): + result = _read_codex_access_token() + + assert result == valid_jwt + def test_missing_returns_none(self, tmp_path, monkeypatch): hermes_home = tmp_path / "hermes" hermes_home.mkdir(parents=True, exist_ok=True) @@ -238,6 +252,24 @@ class TestAnthropicOAuthFlag: assert mock_build.call_args.args[0] == "sk-ant-oat01-pooled" +class TestTryCodex: + def test_pool_without_selected_entry_falls_back_to_auth_store(self): + with ( + patch("agent.auxiliary_client._select_pool_entry", return_value=(True, None)), + patch("agent.auxiliary_client._read_codex_access_token", return_value="codex-auth-token"), + patch("agent.auxiliary_client.OpenAI") as mock_openai, + ): + mock_openai.return_value = MagicMock() + from agent.auxiliary_client import _try_codex + + client, model = _try_codex() + + assert client is not None + assert model == "gpt-5.2-codex" + assert mock_openai.call_args.kwargs["api_key"] == "codex-auth-token" + assert mock_openai.call_args.kwargs["base_url"] == "https://chatgpt.com/backend-api/codex" + + class TestExpiredCodexFallback: """Test that expired Codex tokens don't block the auto chain."""