Skip to content

Bug: openai-codex-responses rejects opaque apiKey tokens before custom proxy request #703

@mastertyko

Description

@mastertyko

Bug Description

We are using GJC with a Codex-compatible proxy backend and the openai-codex-responses WebSocket transport.

Example provider config:

providers:
  custom-codex-proxy:
    baseUrl: http://127.0.0.1:2455/backend-api/codex
    api: openai-codex-responses
    auth: apiKey
    apiKeyEnv: CUSTOM_CODEX_PROXY_API_KEY

CUSTOM_CODEX_PROXY_API_KEY is an opaque proxy API key, not an official ChatGPT/Codex JWT.

Actual Behavior

GJC fails locally before the request reaches the proxy:

Failed to extract accountId from token

The reason is that openai-codex-responses tries to decode chatgpt_account_id from the bearer token and requires it.

Expected Behavior

When auth: apiKey is used with a custom Codex-compatible baseUrl, GJC should pass the bearer token through to the backend.

The client should not require or send chatgpt-account-id unless it can actually decode one from an official ChatGPT/Codex token.

Use Case

We want GJC to connect to a Codex-compatible proxy through the Codex WebSocket endpoint:

ws://127.0.0.1:2455/backend-api/codex/responses

The proxy authenticates the opaque bearer token itself and handles upstream account routing.

Area

Provider / Model support

Suggested Fix

We solved this locally with a small patch to openai-codex-responses.ts:

  • Make accountId optional.
  • Change getAccountId(accessToken) so it returns getCodexAccountId(accessToken) directly instead of throwing when no account id is found.
  • In createCodexHeaders(...), always set Authorization: Bearer <token>.
  • Only set chatgpt-account-id when accountId exists.
  • If accountId is missing, delete/omit chatgpt-account-id.
  • Use a stable fallback namespace for WebSocket session keys, e.g. opaque.

After this patch, GJC successfully connects to the proxy via WebSocket using an opaque proxy key, and the proxy logs show the WebSocket endpoint being accepted.

A minimal behavior change would be:

function getAccountId(accessToken: string): string | undefined {
  return getCodexAccountId(accessToken);
}

and:

headers.set("Authorization", `Bearer ${accessToken}`);

if (accountId) {
  headers.set(OPENAI_HEADERS.ACCOUNT_ID, accountId);
} else {
  headers.delete(OPENAI_HEADERS.ACCOUNT_ID);
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions