Skip to content

feat(auth): support multiple OpenAI/Codex subscriptions - #68520

Open
mehmetkr-31 wants to merge 1 commit into
NousResearch:mainfrom
mehmetkr-31:feat/multiple-codex-subscriptions
Open

feat(auth): support multiple OpenAI/Codex subscriptions#68520
mehmetkr-31 wants to merge 1 commit into
NousResearch:mainfrom
mehmetkr-31:feat/multiple-codex-subscriptions

Conversation

@mehmetkr-31

Copy link
Copy Markdown
Contributor

Problem

Currently only a single OpenAI ChatGPT Codex subscription is supported. When rate limit hits, users cannot switch to another active subscription.

Each Codex CLI has a ~/.codex/auth.json, which one can import (functionality already exists). But importing multiple accounts requires running the device-code OAuth flow for each account — even when the user already has valid auth.json files.

Solution

Multi-account import support for openai-codex:

Command Behavior
hermes auth add openai-codex Device-code OAuth flow (unchanged)
hermes auth add openai-codex --auth-file <path> Import a single account from a specific auth.json file
hermes auth add openai-codex --codex-dir <dir> Import all valid accounts from a directory of auth*.json files

Multi-account workflow

# Log in Account A in Codex CLI
# Copy: ~/.codex/auth.json → ~/.codex/auth.account-a.json
# Log out, log in Account B in Codex CLI
# Copy: ~/.codex/auth.json → ~/.codex/auth.account-b.json

hermes auth add openai-codex --codex-dir ~/.codex
# Imports both accounts as distinct pool entries

Each imported account becomes a distinct, self-contained manual:device_code pool entry — the same pattern already used by xai-oauth, qwen-oauth, and minimax-oauth. The credential pool's round-robin / fill-first / least-used rotation strategies then handle rate-limit failover automatically.

Safety

  • Expired tokens are rejected at import time (they cannot be refreshed)
  • _import_codex_cli_tokens now accepts an optional auth_path parameter for reading from non-default locations
  • _import_codex_cli_tokens_from_directory scans a directory for auth*.json files

Tests

Added 5 new tests in tests/hermes_cli/test_auth_commands.py:

  • Single-file import (--auth-file)
  • Multi-account directory import (--codex-dir)
  • Expired token rejection
  • Empty directory rejection
  • --auth-file vs --codex-dir precedence

All 58 tests pass.

Fixes #65735

@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/cli CLI entry point, hermes_cli/, setup wizard provider/openai OpenAI / Codex Responses API area/auth Authentication, OAuth, credential pools labels Jul 21, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for addressing the remaining auth.json-import gap after multi-account device-code login shipped.

Problems

  • hermes_cli/auth.py:3747 scans and imports every auth*.json without deduplication. In the documented workflow, the final directory contains auth.json for Account B and auth.account-b.json with the same account, so Account B is added twice. CredentialPool.add_entry() appends entries unconditionally (agent/credential_pool.py:2091-2096), while Codex refresh tokens are explicitly single-use (agent/credential_pool.py:1132-1140).

Suggested changes

  • Deduplicate the directory results by stable credential/account material before adding pool entries, and add a test with auth.json, auth.account-a.json, and auth.account-b.json asserting two resulting entries.
  • Document the new flags in the CLI reference.

Automated hermes-sweeper review.

Comment thread hermes_cli/auth.py
"""
dir_path = Path(directory).expanduser()
if not dir_path.is_dir():
return []

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The documented workflow leaves the latest Account B in both auth.json and auth.account-b.json, so this glob imports B twice. Please deduplicate directory results by stable credential/account material before adding pool entries, and cover that three-file/two-account case. Duplicate Codex entries can independently spend the same single-use refresh token.

@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users labels Jul 30, 2026
Currently only a single OpenAI ChatGPT Codex subscription is supported.
When rate limit hits, users cannot switch to another active subscription.

This adds multi-account import support for openai-codex:

- `hermes auth add openai-codex --auth-file <path>`
  Import a single Codex account from a specific auth.json file
  (e.g. ~/.codex/auth.account-b.json). No device-code flow needed.

- `hermes auth add openai-codex --codex-dir <dir>`
  Import all valid Codex accounts from a directory of auth*.json files
  (e.g. ~/.codex). Supports the "log in A → copy auth.json → log in B"
  workflow.

Each imported account becomes a distinct, self-contained
``manual:device_code`` pool entry — the same pattern already used by
xai-oauth, qwen-oauth, and minimax-oauth. The credential pool's
round-robin / fill-first / least-used rotation strategies then handle
rate-limit failover automatically.

Expired tokens are rejected at import time (they cannot be refreshed).

Imports are de-duplicated by account identity. The copy-then-relogin
workflow routinely leaves one account in two files (the live auth.json
plus the labelled copy of it), and re-running the import would append a
second entry for an already-pooled account. Since Codex refresh tokens
are single-use, two entries sharing a pair strand each other the first
time one rotates. codex_credential_identity() keys on the JWT's
chatgpt_account_id so the same account collapses even when the two files
hold different token pairs, falling back to the refresh token when the
claim is absent. The account-id extraction reuses the existing
_decode_jwt_claims() helper and replaces the inline copy of the same
claim lookup in the Codex usage probe.

Fixes NousResearch#65735

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mehmetkr-31
mehmetkr-31 force-pushed the feat/multiple-codex-subscriptions branch from fc9b3c5 to 095b7a0 Compare July 30, 2026 06:31
@mehmetkr-31

Copy link
Copy Markdown
Contributor Author

Reworked per the sweeper review — the duplicate-import defect is fixed and the branch is rebuilt on current origin/main (98e43be8e0). It was DIRTY; it is now MERGEABLE as a single commit.

The double-add is real and now closed. Confirmed the mechanics the review described: CredentialPool.add_entry() appends unconditionally, and Codex refresh tokens are single-use — agent/credential_pool.py says so explicitly in _sync_codex_entry_from_auth_store ("Codex refresh tokens are single-use too, so a fresh refresh_token from another process means our entry's pair is consumed/stale"). So two entries for one account don't just look untidy: the first rotation consumes the pair and strands the duplicate on dead credentials.

  • De-dup by account identity, not just by token. Added codex_credential_identity() in hermes_cli/auth.py, keyed on the JWT's chatgpt_account_id claim so the same account collapses even when the two files hold different token pairs (a re-login, not a byte copy). It falls back to the refresh token — the material that must not be duplicated — then to the access token.
  • Applied at both places the same defect appears. _import_codex_cli_tokens_from_directory() now de-dupes within the scanned directory, and auth_add_command additionally skips accounts already in the pool, so re-running --codex-dir ~/.codex is idempotent instead of appending a second entry each time. The output reports skips (Imported 1 … (1 already in the pool)). I treated the re-run case as in scope because it is the identical failure mode; happy to split it out if you'd rather keep this PR to the directory scan alone.
  • Reused the existing helper rather than adding one. The account-id extraction goes through the module's existing _decode_jwt_claims(), and I replaced the inline copy of the same claim lookup in the Codex usage probe with a call to the new _codex_chatgpt_account_id() — so this removes a duplication instead of adding one.
  • Documented the flags in website/docs/reference/cli-commands.md under hermes auth: --auth-file, --codex-dir, the precedence rule, expiry rejection, and the de-dup behaviour.

Tests — including the exact case the review asked for:

  • test_auth_add_codex_dir_dedupes_same_account_across_filesauth.json (B) + auth.account-a.json (A) + auth.account-b.json (B) asserts two entries, and additionally asserts each single-use refresh token appears exactly once.
  • test_auth_add_codex_dir_dedupes_by_account_id_not_just_token — same account, two different token pairs, one entry. This is the case a refresh-token-only key would miss.
  • test_auth_add_codex_dir_rerun_does_not_duplicate_pool_entries — importing twice stays at one entry.

All three fail with the de-dup reverted and pass with it in place. Suite: tests/hermes_cli/test_auth_commands.py + test_auth_codex_self_heal.py + test_codex_models.py = 33 passed; -k codex across tests/hermes_cli/ and tests/agent/ = 369 passed, 4 skipped.

Two notes from the salvage:

  • Dropped the scripts/release.py hunkLEGACY_AUTHOR_MAP is frozen and contributors/emails/mehmet.kar@std.yildiz.edu.tr already exists on main resolving to mehmetkr-31.
  • The rebase surfaced that main's test-prune wave (6b81590c55) removed two tests my old base still carried; I kept main's deletions rather than resurrecting them. The _import_codex_cli_tokens signature change also required widening one surviving mock in test_auth_codex_self_heal.py to lambda *a, **k:.

@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown

This was generated by AI during triage.

Summary

Of the three surfaced PRs, #68520 directly addresses the remaining Codex auth.json import gap with single-file and directory import plus account deduplication; #58146 addresses stale cooldown state across profiles, while #49079 adds bulk credential removal and does not address multi-subscription import or rotation.

Related pull requests

  • fix(auth): support removing all pooled credentials #49079 related — (+239/-27) — n/a: Adds hermes auth remove <provider> all, preserving source-specific cleanup and guarding exact or ambiguous all labels; it does not change Codex account import or rate-limit failover. The automated keep-open review identifies this as a real command gap but requests documentation for the destructive form.
  • fix: reset auth cooldowns across profiles #58146 related — (+341/-3) — n/a: Extends auth reset to clear persisted cooldown and exhaustion fields across profile-local stores without materializing inherited credentials, providing adjacent remediation for stale rate-limit state rather than multi-account import. The visible diff includes the scope and mutual-exclusion tests requested by the automated keep-open review.
  • feat(auth): support multiple OpenAI/Codex subscriptions #68520 best fix — (+547/-20) — n/a: The recorded best fix adds --auth-file and --codex-dir, imports distinct Codex subscriptions into the credential pool, documents the workflow, and deduplicates directory and existing-pool entries by account identity or token material. The contributor's COMMENTED keep-open review blocked the earlier duplicate-import behavior because Codex refresh tokens are single-use; the visible diff explicitly addresses that objection with three-file/two-account, account-ID, and rerun-deduplication coverage.

Suggested consolidation

Keep #68520 open with a salvage path: retain its Codex file/directory import, stable-account deduplication, tests, and documentation, and obtain contributor re-review of the addressed single-use-refresh-token objection before further disposition. #49079 and #58146 are not duplicates of #68520 and should remain on their separate keep-open tracks for bulk removal and cross-profile cooldown reset, respectively; no merge recommendation is supported here.

Complex graph

flowchart LR
    classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
    classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
    classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
    classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
    classDef best stroke-width:3px,stroke:#b45309
    classDef target stroke-width:3px,stroke:#4338ca
    I65735(["issue #65735 (closed)"])
    P68520["PR #68520 (open)"]
    P68520 -->|best fix| I65735
    class I65735 closed
    class P68520 open
    class P68520 best
    class P68520 target
    click I65735 "https://github.com/NousResearch/hermes-agent/issues/65735"
    click P68520 "https://github.com/NousResearch/hermes-agent/pull/68520"
Loading

Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label).

Cross-PR triage: Reviewed 3 pull requests and 1 issue in this complex. Each diff was read against this issue; Assessment working set: 56 kB of PR diffs, 5 kB of issue/PR text, 6 kB of discussion (8 comments), 3 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

@dlukt

dlukt commented Aug 14, 2026

Copy link
Copy Markdown

Is this going to receive any attention soon?
1 subscription is always going to waste.

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 P3 Low — cosmetic, nice to have provider/openai OpenAI / Codex Responses API sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Support multiple openai/codex subscriptions

5 participants