Skip to content

fix: add ChatGPT-Account-ID and User-Agent headers for openai-codex provider#5169

Closed
lesterli wants to merge 1 commit into
NousResearch:mainfrom
OPENSCI-COMMON:fix/codex-chatgpt-account-headers
Closed

fix: add ChatGPT-Account-ID and User-Agent headers for openai-codex provider#5169
lesterli wants to merge 1 commit into
NousResearch:mainfrom
OPENSCI-COMMON:fix/codex-chatgpt-account-headers

Conversation

@lesterli

@lesterli lesterli commented Apr 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Requests to chatgpt.com/backend-api/codex are silently blocked by Cloudflare's
managed challenge when ChatGPT-Account-ID and a recognisable User-Agent are
missing. The agent receives an HTML JS-challenge page instead of JSON, logged as
Non-retryable client error: <html>…Enable JavaScript and cookies….

This PR adds both required headers to every OpenAI client targeting the Codex backend.

Root cause

The Codex CLI (codex_cli_rs) sends ChatGPT-Account-ID and a versioned
User-Agent on every request. Hermes only sends Authorization: Bearer — no
account header, no identifiable user-agent. Cloudflare's bot-detection layer on
chatgpt.com then serves a JS challenge page that the OpenAI Python SDK cannot
handle.

This likely also explains:

Changes

File What
hermes_cli/auth.py Extract account_id from auth store tokens; new cached codex_default_headers() function
run_agent.py Inject headers at 3 code paths: agent init, credential refresh, credential swap
agent/auxiliary_client.py Inject headers at 2 code paths: _try_codex(), resolve_provider_client()

Headers sent

User-Agent: hermes-agent/{version} ({os} {release}; {arch})
ChatGPT-Account-ID: {account_id from auth.json tokens}

The account_id is already stored in ~/.hermes/auth.json under
providers.openai-codex.tokens.account_id (populated during OAuth login
and Codex CLI credential migration).

Design decisions

  • Cached: Headers are built once per process and reused. force_refresh=True
    is only called after credential rotation in _try_refresh_codex_client_credentials.
  • Placed in hermes_cli/auth.py: Follows the pattern of copilot_default_headers()
    in hermes_cli/models.py — provider-specific header builders live near their
    auth logic.
  • Narrowed exceptions: Only catches AuthError, FileNotFoundError, KeyError
    (not bare Exception), with logger.debug for visibility.
  • Uses hermes_cli.__version__: Avoids hardcoded version strings.

Test plan

  • Configure model.provider: openai-codex and send a message — response received
    (previously: Cloudflare HTML block → silent failure)
  • Verify headers: python -c "from hermes_cli.auth import codex_default_headers; print(codex_default_headers())"
  • Verify caching: second call returns same object
  • Verify force_refresh=True rebuilds headers
  • Test without Codex credentials: should gracefully skip ChatGPT-Account-ID

🤖 Generated with Claude Code

…rovider

Requests to chatgpt.com/backend-api/codex are blocked by Cloudflare when
ChatGPT-Account-ID header and a recognisable User-Agent are missing. The
agent receives an HTML JS-challenge page instead of JSON, causing silent
failures logged as "Non-retryable client error".

This adds a cached `codex_default_headers()` function in hermes_cli/auth.py
that provides:
- User-Agent: hermes-agent/{version} ({os} {release}; {arch})
- ChatGPT-Account-ID: {account_id from auth store tokens}

Headers are injected at all 5 code paths that create OpenAI clients for the
Codex backend (main agent init, credential refresh, credential swap,
auxiliary client, and raw provider client).

Likely also addresses:
- #2176 (account bans — requests without account header may be flagged)
- #1167 (usage shown as "Other" — no client identification headers)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@lesterli lesterli closed this Apr 7, 2026
@lesterli lesterli deleted the fix/codex-chatgpt-account-headers branch April 7, 2026 10:00
saamuelng601-pixel added a commit to saamuelng601-pixel/hermes-agent that referenced this pull request Apr 16, 2026
ChatGPT's Codex backend sits behind Cloudflare's managed challenge. When
Hermes hits /responses or /chat/completions via the Python openai SDK,
Cloudflare sees the default "OpenAI/Python X.X.X" User-Agent and the
absence of ChatGPT-Account-Id, classifies the request as bot traffic, and
returns an HTML JS-challenge page that the openai SDK cannot handle.
Hermes reports this as "Non-retryable client error: <html>..." and
falls back to the secondary provider even when the Codex credentials
themselves are fresh and the account is under-quota.

The official Rust codex_cli_rs CLI sends three headers that Cloudflare
uses to verify the client:
  - User-Agent: codex_cli_rs/<version>
  - originator: codex_cli_rs
  - ChatGPT-Account-Id: <decoded from JWT>

This change installs an httpx.Client.send / AsyncClient.send wrapper that
inspects the outbound request, detects chatgpt.com hosts, and injects the
three headers (decoding ChatGPT-Account-Id from the bearer JWT). This
covers every OpenAI client Hermes creates — including auxiliary flows
(_try_codex, resolve_provider_client, _to_async_client) that PR NousResearch#6391
doesn't reach.

Tested locally: requests to /responses now return 200 (previously HTML 403).
/chat/completions returns 404 (backend doesn't expose that route for
Codex), which is the expected behavior — callers should use the
Responses API adapter.

Refs: NousResearch#6391 (upstream CF fix, scoped to main agent only), NousResearch#5169 (closed
earlier attempt), NousResearch#10016 (browser-compat Codex login roadmap).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant