From 40527ff5e35db7fc6f501d8aa5997caa4a382a79 Mon Sep 17 00:00:00 2001 From: tymrtn Date: Mon, 6 Apr 2026 21:12:57 +0200 Subject: [PATCH] fix(auth): actionable error message when Codex refresh token is reused When the Codex CLI (or VS Code extension) consumes a refresh token before Hermes can use it, Hermes previously surfaced a generic 401 error with no actionable guidance. - In `refresh_codex_oauth_pure`: detect `refresh_token_reused` from the OAuth endpoint and raise an AuthError explaining the cause and the exact steps to recover (run `codex` to refresh, then `hermes login`). - In `run_agent.py`: when provider is `openai-codex` and HTTP 401 is received, show Codex-specific recovery steps instead of the generic "check your API key" message. Co-Authored-By: Claude Sonnet 4.6 --- hermes_cli/auth.py | 8 ++++++++ run_agent.py | 16 +++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/hermes_cli/auth.py b/hermes_cli/auth.py index 5a02c9233..bfbeb8182 100644 --- a/hermes_cli/auth.py +++ b/hermes_cli/auth.py @@ -1030,6 +1030,14 @@ def refresh_codex_oauth_pure( pass if code in {"invalid_grant", "invalid_token", "invalid_request"}: relogin_required = True + if code == "refresh_token_reused": + message = ( + "Codex refresh token was already consumed by another client " + "(e.g. Codex CLI or VS Code extension). " + "Run `codex` in your terminal to generate fresh tokens, " + "then run `hermes login --provider openai-codex` to re-authenticate." + ) + relogin_required = True raise AuthError( message, provider="openai-codex", diff --git a/run_agent.py b/run_agent.py index d85682a16..d97e08ad5 100644 --- a/run_agent.py +++ b/run_agent.py @@ -8187,11 +8187,17 @@ class AIAgent: self._vprint(f"{self.log_prefix} 🌐 Endpoint: {_base}", force=True) # Actionable guidance for common auth errors if status_code in (401, 403) or "unauthorized" in error_msg or "forbidden" in error_msg or "permission" in error_msg: - self._vprint(f"{self.log_prefix} 💡 Your API key was rejected by the provider. Check:", force=True) - self._vprint(f"{self.log_prefix} • Is the key valid? Run: hermes setup", force=True) - self._vprint(f"{self.log_prefix} • Does your account have access to {_model}?", force=True) - if "openrouter" in str(_base).lower(): - self._vprint(f"{self.log_prefix} • Check credits: https://openrouter.ai/settings/credits", force=True) + if _provider == "openai-codex" and status_code == 401: + self._vprint(f"{self.log_prefix} 💡 Codex OAuth token was rejected (HTTP 401). Your token may have been", force=True) + self._vprint(f"{self.log_prefix} refreshed by another client (Codex CLI, VS Code). To fix:", force=True) + self._vprint(f"{self.log_prefix} 1. Run `codex` in your terminal to generate fresh tokens.", force=True) + self._vprint(f"{self.log_prefix} 2. Then run `hermes login --provider openai-codex` to re-authenticate.", force=True) + else: + self._vprint(f"{self.log_prefix} 💡 Your API key was rejected by the provider. Check:", force=True) + self._vprint(f"{self.log_prefix} • Is the key valid? Run: hermes setup", force=True) + self._vprint(f"{self.log_prefix} • Does your account have access to {_model}?", force=True) + if "openrouter" in str(_base).lower(): + self._vprint(f"{self.log_prefix} • Check credits: https://openrouter.ai/settings/credits", force=True) else: self._vprint(f"{self.log_prefix} 💡 This type of error won't be fixed by retrying.", force=True) logging.error(f"{self.log_prefix}Non-retryable client error: {api_error}")