Skip to content

fix(auth): self-heal Codex OAuth token drift - #45573

Merged
teknium1 merged 3 commits into
mainfrom
hermes/hermes-2b0b0d4e
Jun 13, 2026
Merged

fix(auth): self-heal Codex OAuth token drift#45573
teknium1 merged 3 commits into
mainfrom
hermes/hermes-2b0b0d4e

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Codex OAuth recovery now handles both stale refresh_token rotation and the cron-time missing access_token state by adopting a valid Codex CLI token pair before asking the user to re-authenticate.

Changes

  • hermes_cli/auth.py: shared Codex CLI token adoption helper; uses it for rejected refresh tokens and malformed/missing singleton token state.
  • tests/hermes_cli/test_auth_codex_self_heal.py: covers stale refresh tokens, malformed imports, and the exact missing-access-token cron failure path.
  • tests/hermes_cli/test_auth_codex_provider.py: isolates the legacy missing-access-token error test from host Codex CLI credentials.
  • scripts/release.py: maps @Kenmege's author email for attribution checks.

Validation

Check Result
python -m py_compile hermes_cli/auth.py tests/hermes_cli/test_auth_codex_self_heal.py tests/hermes_cli/test_auth_codex_provider.py pass
scripts/run_tests.sh tests/hermes_cli/test_auth_codex_self_heal.py tests/hermes_cli/test_auth_codex_provider.py 36 passed
Isolated E2E resolve_codex_runtime_credentials() with malformed Hermes auth + valid CODEX_HOME/auth.json recovered fresh-access and persisted both tokens

Salvages #45261 with @Kenmege's authorship preserved, then widens the fix to the missing access_token path from the reported cron failure.

Infographic

Codex OAuth Self-Heal

Kennedy Umege and others added 3 commits June 13, 2026 04:56
… ~/.codex

Hermes keeps its own copy of the Codex OAuth token per profile and at the
top level, separate from the Codex CLI's ~/.codex/auth.json. OAuth
refresh_tokens are single-use, so when the Codex CLI (or another Hermes
process) rotates the shared token, the frozen copy's refresh_token goes
stale and refresh_codex_oauth_pure fails with a relogin-required error
(invalid_grant / refresh_token_reused / 401). Today that surfaces as a hard
401 on the turn — idle profiles and desktop sessions 401 "token_expired"
until a manual re-auth — even though ~/.codex/auth.json holds a fresh token.

_refresh_codex_auth_tokens now falls back to _import_codex_cli_tokens() (the
canonical Codex CLI store) when the stored refresh_token is rejected, adopts
and persists the fresh token, and lets the in-flight retry succeed. This
complements PR #6525 (force relogin on 401/403): we attempt automatic
recovery before surfacing a relogin prompt. Transient failures (e.g. 429
quota, relogin_required=False) are never self-healed — the stored token is
still valid there — so they re-raise unchanged, and the happy path is
untouched.

Adds tests/hermes_cli/test_auth_codex_self_heal.py covering: self-heal on
invalid_grant, no self-heal on 429 quota, re-raise when ~/.codex is absent,
and happy-path-unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r email

Addresses PR review feedback:
- Validate refresh_token (not only access_token) before persisting the
  re-imported Codex token, so a half-token payload can't silently break the
  next refresh cycle.
- Make the recovery log path-agnostic ("Codex CLI auth.json") since
  _import_codex_cli_tokens can read $CODEX_HOME, not only ~/.codex.
- Add regression test: relogin-required + imported token missing refresh_token
  -> re-raise and persist nothing.
- Map kenmege@yahoo.com -> Kenmege in scripts/release.py AUTHOR_MAP
  (fixes the check-attribution job).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Recover Codex singleton auth entries that have a refresh token but no access token by adopting a valid Codex CLI token pair, matching the cron-time failure mode before falling back to the credential pool.
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-2b0b0d4e vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 10889 on HEAD, 10889 on base (➖ 0)

🆕 New issues (3):

Rule Count
invalid-assignment 1
no-matching-overload 1
unresolved-import 1
First entries
tests/run_agent/test_credits_notices_toggle.py:76: [invalid-assignment] invalid-assignment: Object of type `None` is not assignable to attribute `_credits_session_start_micros` of type `int`
hermes_cli/auth.py:3808: [no-matching-overload] no-matching-overload: No overload of `dict.__init__` matches arguments
tests/hermes_cli/test_auth_codex_self_heal.py:15: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`

✅ Fixed issues (2):

Rule Count
unresolved-attribute 2
First entries
tests/run_agent/test_credits_notices_toggle.py:76: [unresolved-attribute] unresolved-attribute: Unresolved attribute `_credits_session_start_micros` on type `AIAgent`
run_agent.py:2891: [unresolved-attribute] unresolved-attribute: Object of type `Self@get_credits_spent_micros` has no attribute `_credits_session_start_micros`

Unchanged: 5709 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@teknium1
teknium1 merged commit aa07983 into main Jun 13, 2026
28 checks passed
@teknium1
teknium1 deleted the hermes/hermes-2b0b0d4e branch June 13, 2026 12:15
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cli CLI entry point, hermes_cli/, setup wizard area/auth Authentication, OAuth, credential pools provider/copilot GitHub Copilot (ACP + Chat) labels Jun 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/auth Authentication, OAuth, credential pools comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists provider/copilot GitHub Copilot (ACP + Chat) type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants