RFC: ChatGPT Account OAuth Support for OpenAI Codex Models¶
Status¶
Draft.
Summary¶
Keen Code currently supports OpenAI through API-key authentication against the OpenAI Responses API. This RFC proposes adding a separate provider path for users who want to authenticate with a ChatGPT account through browser OAuth and use the ChatGPT/Codex model endpoint without entering an API key or base URL.
The new provider should be presented as:
OpenAIfor the existing OpenAI Platform API-key flow.Codex (ChatGPT OAuth)for browser-based ChatGPT account OAuth.
The ChatGPT/Codex flow is not a normal OpenAI API-key variant. It uses a different credential lifecycle, request endpoint, request headers, model list, and user-facing setup flow. The implementation should keep it separate from the current openai provider instead of forcing OAuth into the existing ProviderConfig.APIKey field.
Goals¶
- Add browser-only OAuth login for ChatGPT accounts.
- Let users select
Codex (ChatGPT OAuth)from/model. - Open the default browser to authenticate with OpenAI.
- Show a local browser success page after authentication finishes.
- Return the user to the CLI and continue to model selection.
- Skip API key and base URL prompts for the ChatGPT/Codex provider.
- Store OAuth tokens locally with file permissions appropriate for secrets.
- Refresh access tokens automatically.
- Use the Codex responses endpoint for ChatGPT/Codex traffic.
- Keep existing OpenAI API-key behavior unchanged.
Non-Goals¶
- No headless/device-code login flow in the initial implementation.
- No ChatGPT web session scraping.
- No reuse of ChatGPT OAuth credentials against arbitrary OpenAI Platform endpoints.
- No changes to existing non-OpenAI providers.
- No automatic migration of existing
openaiAPI-key configuration to ChatGPT/Codex.
Current State¶
Keen Code currently has these relevant boundaries:
- Provider metadata is embedded from
providers/registry.yaml. internal/config.ProviderConfigstoresModels,APIKey, and optionalBaseURL.config.Resolverequires an API key for all configured providers.llm.NewClientrejects configs with an empty API key./modelis implemented ininternal/cli/repl/widgets/model_selection.go.- The model selection widget currently prompts for model, thinking effort, optional base URL, and API key.
- The existing OpenAI client is
OpenAIResponsesClient, backed bygithub.com/openai/openai-go.
Those assumptions fit API-key providers. They do not fit ChatGPT/Codex OAuth cleanly.
Proposed Design¶
Add a separate provider ID:
openai-codex
Display it as:
Codex (ChatGPT OAuth)
Keep the existing openai provider but rename its display name to:
OpenAI
The new provider has:
- no base URL prompt
- no API key prompt
- browser OAuth before model selection when credentials are missing or invalid
- its own token storage
- its own LLM client implementation
- its own static model list
User Experience¶
First-Time Setup¶
- User runs Keen Code or enters
/model. - User selects
Codex (ChatGPT OAuth). - If no valid OAuth credentials exist, Keen Code starts a temporary localhost callback server.
- Keen Code opens the OAuth URL in the default browser.
- User authenticates with their OpenAI/ChatGPT account.
- Browser redirects to the local callback URL.
- Keen Code validates state, exchanges the code for tokens, stores credentials, then returns a success page.
- User returns to the CLI.
- Keen Code shows the static supported ChatGPT/Codex model list.
- User selects a model and thinking effort when that model supports configurable effort.
- Keen Code saves the selected provider/model and initializes the LLM client.
Returning Setup¶
If valid OAuth credentials already exist:
- User selects
Codex (ChatGPT OAuth). - Keen Code skips login.
- Keen Code shows the static ChatGPT/Codex model list.
- User selects a model and thinking effort when that model supports configurable effort.
If credentials exist but the access token is expired, model selection can continue. The LLM client should refresh the access token before the next request.
Provider Selection Flow¶
flowchart TD
A[User runs /model] --> B[Select provider]
B --> C{Provider}
C -->|OpenAI API Key| D[Select model]
D --> E[Select thinking effort if supported]
E --> F[Prompt for base URL]
F --> G[Prompt for API key]
G --> H[Save config and initialize API-key client]
C -->|OpenAI ChatGPT/Codex| I{OAuth credentials exist?}
I -->|No| J[Start local callback server]
J --> K[Open browser to OpenAI OAuth URL]
K --> L[Exchange callback code for tokens]
L --> M[Store OAuth credentials]
I -->|Yes| N[Select Codex model]
M --> N
N --> O[Select thinking effort if supported]
O --> P[Save provider/model only]
P --> Q[Initialize Codex OAuth client]
OAuth Flow¶
The browser login should use Authorization Code with PKCE.
Suggested constants, matching the Codex-style flow used by OpenCode:
issuer: https://auth.openai.com
client_id: app_EMoamEEZ73f0CkXaXp7hrann
scope: openid profile email offline_access
redirect_uri: http://localhost:<port>/auth/callback
authorization flags:
id_token_add_organizations=true
codex_cli_simplified_flow=true
originator=keen-code
The port can be fixed or dynamic. A dynamic port avoids collisions, but the redirect URI must be accepted by the OpenAI OAuth client. If only a known localhost port works for this client, use that port and handle conflicts explicitly.
OAuth Sequence¶
sequenceDiagram
participant CLI as Keen CLI
participant Local as Local Callback Server
participant Browser
participant Auth as auth.openai.com
CLI->>CLI: Generate PKCE verifier/challenge
CLI->>CLI: Generate random state
CLI->>Local: Listen on localhost callback URL
CLI->>Browser: Open authorization URL
Browser->>Auth: User authenticates
Auth->>Browser: Redirect with code and state
Browser->>Local: GET /auth/callback?code=...&state=...
Local->>Local: Validate state
Local->>Auth: POST /oauth/token with code and verifier
Auth-->>Local: access_token, refresh_token, id_token, expires_in
Local->>CLI: Resolve OAuth result
CLI->>CLI: Store credentials
Local-->>Browser: Success page
Important Callback Rules¶
The callback handler should be stricter than the OpenCode implementation reviewed for this issue:
- Validate
statebefore handling success or error outcomes. - Do not reflect raw
errororerror_descriptioninto HTML. - Exchange tokens before showing the browser success page.
- Show an error page if token exchange fails.
- Stop the local HTTP server in a
deferorfinallyequivalent for success, cancel, timeout, and failure. - Time out the pending login after a fixed duration, for example five minutes.
Token Storage¶
Add a separate auth store instead of placing OAuth material inside provider config.
Suggested file:
~/.keen/auth.json
Suggested shape:
{
"openai-codex": {
"type": "oauth",
"access_token": "...",
"refresh_token": "...",
"expires_at": "2026-04-27T12:00:00Z",
"account_id": "optional ChatGPT account or organization id"
}
}
The file should be written with 0600 permissions. The code should avoid logging tokens, authorization codes, full callback URLs, or OAuth response bodies.
Token Claims¶
Extract account_id from the ID token first, then access token as a fallback. Known claim locations from OpenCode:
chatgpt_account_id
https://api.openai.com/auth.chatgpt_account_id
organizations[0].id
JWT parsing here is only for claim extraction, not for validating trust. The access token is trusted because it came directly from the token endpoint over TLS.
Refresh Flow¶
Access tokens should be refreshed lazily before requests when expired or near expiry.
flowchart TD
A[Codex request starts] --> B[Load stored OAuth credentials]
B --> C{Access token valid?}
C -->|Yes| D[Use current access token]
C -->|No| E[Acquire provider refresh lock]
E --> F[Reload credentials]
F --> G{Another request refreshed?}
G -->|Yes| D
G -->|No| H[POST refresh_token to token endpoint]
H --> I[Store new access token]
I --> J[Preserve old refresh token if response omits replacement]
J --> D
D --> K[Send request to Codex endpoint]
Refresh should be guarded by a per-provider mutex to avoid concurrent refreshes racing against each other. This matters if refresh tokens rotate.
Request Path¶
The ChatGPT/Codex client should send Responses-compatible requests to:
https://chatgpt.com/backend-api/codex/responses
Required headers:
Authorization: Bearer <access_token>
ChatGPT-Account-Id: <account_id> # only when known
originator: keen-code
User-Agent: keen-code/<version> (<os> <arch>)
session_id: <session id if available>
Keen Code should not pass an OpenAI API key for this provider.
LLM Client Design¶
Add a new client rather than overloading OpenAIResponsesClient:
internal/llm/openai_codex.go
The client can share request conversion logic with OpenAIResponsesClient where practical:
- message conversion
- tool conversion
- reasoning effort mapping
- streaming event handling
- tool loop behavior
The main differences are:
- auth comes from OAuth token storage, not
ClientConfig.APIKey - endpoint is the ChatGPT Codex endpoint
- refresh is required before requests
- custom headers are required
- base URL is not configurable
The current Keen Code dependency, github.com/openai/openai-go v1.8.2, can target the Codex endpoint cleanly enough to use for the first implementation. client.Responses.NewStreaming posts to the relative path responses, and option.WithBaseURL("https://chatgpt.com/backend-api/codex/") resolves that request to https://chatgpt.com/backend-api/codex/responses.
The Codex client should therefore keep using openai-go response parameter types, streaming decoding, and response event types. It should not use a dummy API key. Instead, refresh OAuth credentials before each request as needed and pass request-specific headers:
stream := c.client.Responses.NewStreaming(ctx, params,
option.WithHeader("authorization", "Bearer "+accessToken),
option.WithHeader("originator", "keen-code"),
option.WithHeader("User-Agent", userAgent),
)
When accountID is known, add:
option.WithHeader("ChatGPT-Account-Id", accountID)
Because openai.NewClient reads OPENAI_API_KEY, OPENAI_ORG_ID, and OPENAI_PROJECT_ID from the environment by default, the Codex client should explicitly override the authorization header per request and avoid passing OpenAI Platform organization/project headers to the ChatGPT/Codex endpoint. If those environment-derived headers are present, remove them with request options such as option.WithHeaderDel("OpenAI-Organization") and option.WithHeaderDel("OpenAI-Project"), or construct the service in a way that avoids default environment options if the SDK exposes that in a future version.
Config Changes¶
Current provider config:
type ProviderConfig struct {
Models []string
APIKey string
BaseURL string
}
Proposed direction:
- Keep
ProviderConfigfor API-key providers. - Add provider metadata or helper functions that identify auth requirements.
- Add a separate
AuthStorefor OAuth credentials. - Allow
config.Resolveandllm.NewClientto resolveopenai-codexwithout an API key.
Possible resolved config extension:
type ResolvedConfig struct {
Provider string
APIKey string
Model string
ThinkingEffort string
BaseURL string
AuthMode string
}
For openai-codex, AuthMode would be oauth, APIKey would be empty, and the LLM client would load OAuth credentials from the auth store.
Provider Registry Changes¶
Add a separate static provider entry to providers/registry.yaml. Keen Code should not discover these models dynamically from the ChatGPT/Codex endpoint in the initial implementation.
- id: openai-codex
name: Codex (ChatGPT OAuth)
models:
- id: gpt-5.5
name: GPT-5.5
context_window: 1050000
thinking_efforts: ["low", "medium", "high", "xhigh"]
- id: gpt-5.4
name: GPT-5.4
context_window: 1050000
thinking_efforts: ["low", "medium", "high", "xhigh"]
- id: gpt-5.3-codex
name: GPT-5.3 Codex
context_window: 400000
thinking_efforts: ["low", "medium", "high", "xhigh"]
Initial supported models:
gpt-5.5gpt-5.4gpt-5.3-codex
Model metadata to use:
| Model | API context window | ChatGPT-account conservative registry context window | Known higher tier window | Max output | Thinking efforts |
|---|---|---|---|---|---|
gpt-5.5 |
1,050,000 | 256,000 | Pro: 400,000 | 128,000 | low, medium, high, xhigh |
gpt-5.4 |
1,050,000 | 256,000 | Pro: 400,000 | 128,000 | low, medium, high, xhigh |
gpt-5.3-codex |
400,000 | 400,000 | Unknown | 128,000 | low, medium, high, xhigh |
The gpt-5.5 and gpt-5.4 registry context windows intentionally differ from the public API context windows. This provider authenticates with ChatGPT accounts, not API organizations. OpenAI's ChatGPT help docs list Thinking context as 256K for all paid tiers and 400k for Pro. Because Keen Code's provider registry is currently static and does not detect the signed-in plan, the first implementation should use the conservative 256000 context window for GPT-5.5 and GPT-5.4. A later enhancement can raise the displayed window to 400000 after detecting a Pro account.
gpt-5.3-codex remains 400000 because the official GPT-5.3-Codex model page lists a 400,000-token context window, and OpenCode's model metadata for the OpenAI provider uses 400000 context, 272000 input, and 128000 output. If real ChatGPT/Codex OAuth testing shows a lower Plus limit for this specific Codex model, the static registry should be reduced to that lower value.
The ChatGPT/Codex provider should not expose off or none in the initial implementation. Users should choose an active reasoning effort from low upward. This keeps the UX consistent across the supported ChatGPT/Codex models and avoids ambiguity between OpenAI's API-level none value and Keen Code's existing off convention, which means "omit the reasoning parameter."
OpenCode handles supported OAuth models by starting from its normal OpenAI catalog and filtering it with a hardcoded allowlist plus Codex-name matching. Keen Code's registry is simpler, so a separate static openai-codex provider list is clearer and easier to test.
Rename existing OpenAI display name:
- id: openai
name: OpenAI
REPL Model Selection Changes¶
The model selection widget needs to branch on provider auth type.
For API-key providers:
Provider -> Model -> Thinking -> Base URL if supported -> API Key -> Complete
For ChatGPT/Codex:
Provider -> OAuth if needed -> Model -> Thinking -> Complete
This likely requires a new model-selection step:
StepOAuth
However, OAuth includes asynchronous work and browser launch. It may be cleaner to trigger OAuth as a tea.Cmd after selecting the provider, then return to the model list on success.
The widget should render clear CLI text while waiting:
Opening your browser to sign in with OpenAI...
After authentication succeeds, return to Keen Code.
If opening the browser fails, print the URL and ask the user to open it manually.
Browser Opening¶
Use OS-specific commands:
- macOS:
open <url> - Linux:
xdg-open <url> - Windows:
rundll32 url.dll,FileProtocolHandler <url>or equivalent
The browser command should not block the REPL. Failure to launch should not fail the flow if the URL can be displayed.
Error Handling¶
Expected user-facing errors:
- OAuth port unavailable.
- Browser launch failed; URL shown for manual opening.
- Login timed out.
- User denied or canceled authorization.
- OAuth callback state mismatch.
- Token exchange failed.
- Token refresh failed.
- Account lacks access to the selected model.
- Codex endpoint returns unauthorized or forbidden.
For refresh failures, Keen Code should surface a clear message suggesting re-authentication.
Security Considerations¶
- Generate high-entropy PKCE verifier and state.
- Bind callback handling to the pending state.
- Prefer
localhostloopback only. - Store tokens with
0600permissions. - Do not print tokens, auth codes, or callback URLs with query strings in logs.
- HTML-escape browser error text.
- Stop the local callback server after each attempt.
- Guard token refresh with a mutex.
- Preserve old refresh token if refresh response does not include a new one.
- Treat OAuth credentials separately from API keys to avoid accidental exposure in config views.
Compatibility and Migration¶
No migration is required for existing users.
Existing users keep:
openai -> OpenAI
New users can choose:
openai-codex -> Codex (ChatGPT OAuth)
If a user switches between providers, Keen Code should preserve each provider's selected models and credentials independently.
Testing Strategy¶
Unit tests:
- PKCE verifier/challenge format.
- State generation length and uniqueness properties.
- JWT account ID extraction.
- Callback handler rejects missing or mismatched state.
- Callback handler escapes error text.
- Token exchange success and failure.
- Token refresh success and failure.
- Refresh preserves old refresh token when omitted.
- Refresh mutex prevents duplicate refresh calls.
- Config resolution allows
openai-codexwithout API key. - Config resolution still rejects missing API keys for API-key providers.
- Model selection skips base URL and API key for
openai-codex. - Provider registry includes
openai-codex.
Integration-style tests with fake HTTP servers:
- Browser OAuth callback succeeds and stores credentials.
- Expired token refreshes before a Codex request.
- Codex request includes Authorization and optional ChatGPT-Account-Id headers.
- Codex request targets
/backend-api/codex/responses. - Unauthorized refresh produces a re-authentication error.
Manual tests:
- First-time login on macOS.
- Existing credential reuse.
- Expired credential refresh.
- Browser launch failure fallback by forcing an invalid opener.
- OAuth timeout.
/modelswitching between API-key OpenAI and ChatGPT/Codex.
Rollout Plan¶
- Add the provider and config/auth primitives behind normal tests.
- Add OAuth login and storage.
- Add the Codex LLM client.
- Wire
/modelto the new flow. - Run end-to-end manual login.
- Update user-facing docs or README.
Open Questions¶
- Does the Codex OAuth client accept a dynamic localhost port, or must Keen Code use a fixed callback port?
- Keen Code should bind localhost:1455, fail with a clear message if unavailable, and not silently pick another port. Since we are not supporting headless, the fallback can simply tell the user to close the app using port 1455 and retry.
- Are
gpt-5.5,gpt-5.4, andgpt-5.3-codexall accepted by the ChatGPT/Codex endpoint for the target account plans? - Yes.
- Should
openai-codexselected model history be stored inProviderConfig.Models, or should OAuth providers have a distinct config shape? - Existing one is fine.
- Should token storage live in
~/.keen/auth.jsonimmediately, or should it integrate with OS keychains later? - Yes
~/.keen/auth.jsonis fine. - Should Keen Code expose a
/logoutor/authcommand in the same issue, or leave it for follow-up? - We can have a
/logoutcommand to logout the current user. It should be simple and just remove the auth credentials from the auth file. Perhaps also show a message to the user that the logout is successful.
Fine-Grained TODO List¶
Research and Validation¶
- [ ] Verify the current Codex OAuth authorization URL parameters against a real browser login.
- [ ] Verify whether
redirect_urisupports dynamic localhost ports. - [ ] Verify the exact browser callback path accepted by the OpenAI OAuth client.
- [ ] Verify the current Codex responses endpoint.
- [ ] Verify that
gpt-5.5,gpt-5.4, andgpt-5.3-codexwork with ChatGPT/Codex auth. - [ ] Verify whether Plus accounts receive a lower effective context window than Pro accounts.
- [ ] Capture representative unauthorized, forbidden, and rate-limit responses from the Codex endpoint.
Provider Registry¶
- [ ] Rename the existing
openaidisplay name toOpenAI. - [ ] Add
openai-codexprovider toproviders/registry.yaml. - [ ] Add static ChatGPT/Codex model entries for
gpt-5.5,gpt-5.4, andgpt-5.3-codex. - [ ] Add or update registry tests for the new provider.
- [ ] Set
gpt-5.5context window to256000. - [ ] Set
gpt-5.4context window to256000. - [ ] Set
gpt-5.3-codexcontext window to400000. - [ ] Set
gpt-5.5thinking efforts to["low", "medium", "high", "xhigh"]. - [ ] Set
gpt-5.4thinking efforts to["low", "medium", "high", "xhigh"]. - [ ] Set
gpt-5.3-codexthinking efforts to["low", "medium", "high", "xhigh"].
Auth Storage¶
- [ ] Add
internal/authpackage or equivalent. - [ ] Define OAuth credential structs.
- [ ] Add auth file path helper for
~/.keen/auth.json. - [ ] Implement auth file load.
- [ ] Implement auth file save with
0600permissions. - [ ] Implement auth get/set/remove for provider IDs.
- [ ] Add tests for missing auth file.
- [ ] Add tests for invalid auth file contents.
- [ ] Add tests for file permission behavior where supported.
OAuth Browser Flow¶
- [ ] Add PKCE verifier generation.
- [ ] Add PKCE S256 challenge generation.
- [ ] Add state generation.
- [ ] Add authorization URL builder.
- [ ] Add token exchange request.
- [ ] Add token refresh request.
- [ ] Add ID/access token claim parser.
- [ ] Add account ID extraction helper.
- [ ] Add localhost callback server.
- [ ] Validate
statebefore handling OAuth success or error. - [ ] Exchange tokens before returning browser success HTML.
- [ ] HTML-escape error messages.
- [ ] Stop callback server after success.
- [ ] Stop callback server after token exchange failure.
- [ ] Stop callback server after timeout.
- [ ] Add browser opener abstraction.
- [ ] Implement macOS browser opener.
- [ ] Implement Linux browser opener.
- [ ] Implement Windows browser opener.
- [ ] Add fallback that prints URL if browser opening fails.
Config Resolution¶
- [ ] Add
ProviderOpenAICodexconstant. - [ ] Add provider auth-mode helper, for example
RequiresAPIKey(providerID string). - [ ] Update
config.Resolveto allowopenai-codexwithout API key. - [ ] Preserve API-key requirement for existing providers.
- [ ] Add
AuthModeor equivalent toResolvedConfigif needed. - [ ] Update root command config hydration for OAuth provider.
- [ ] Add tests for resolving
openai-codex. - [ ] Add regression tests for resolving API-key providers.
Model Selection UI¶
- [ ] Add provider helper for OAuth providers.
- [ ] Add model selection branch for
openai-codex. - [ ] Trigger OAuth after selecting
openai-codexwhen credentials are missing. - [ ] Skip OAuth when stored credentials exist.
- [ ] Skip base URL input for
openai-codex. - [ ] Skip API key input for
openai-codex. - [ ] Save selected provider/model/thinking effort after Codex model selection.
- [ ] Render in-progress OAuth status.
- [ ] Render browser-open fallback URL.
- [ ] Render OAuth failure messages.
- [ ] Add tests for provider selection branching.
- [ ] Add tests for paste handling not appending secrets in OAuth mode.
- [ ] Add tests for completion without API key for
openai-codex.
LLM Client¶
- [ ] Add
OpenAICodexClient. - [ ] Reuse or extract shared OpenAI Responses message conversion.
- [ ] Reuse or extract shared tool conversion.
- [ ] Reuse or extract reasoning effort mapping.
- [ ] Ensure
openai-codexnever exposesoffornonereasoning options. - [ ] Ensure
openai-codexalways sends the selectedlow,medium,high, orxhighreasoning effort. - [ ] Add OAuth token provider dependency to the Codex client.
- [ ] Add refresh-before-request behavior.
- [ ] Add per-provider refresh mutex.
- [ ] Preserve old refresh token if refresh response omits a new token.
- [ ] Add
Authorizationheader. - [ ] Add optional
ChatGPT-Account-Idheader. - [ ] Add
originatorheader. - [ ] Add
User-Agentheader. - [ ] Add session ID header if available in the LLM layer.
- [ ] Send requests to
https://chatgpt.com/backend-api/codex/responses. - [ ] Use
openai-gowithoption.WithBaseURL("https://chatgpt.com/backend-api/codex/")and per-request OAuth headers. - [ ] Add a test proving
openai-gosends Codex requests tohttps://chatgpt.com/backend-api/codex/responses. - [ ] Add a test proving request-specific OAuth authorization overrides any environment-derived OpenAI API-key authorization.
- [ ] Add streaming response parsing tests.
- [ ] Add tool-call loop tests for the Codex client.
- [ ] Add refresh retry/error tests.
Client Factory¶
- [ ] Update
llm.NewClientto acceptopenai-codexwithout API key. - [ ] Route
openai-codextoNewOpenAICodexClient. - [ ] Keep
openairouted toNewOpenAIResponsesClient. - [ ] Add client factory tests for
openai-codex. - [ ] Add regression tests for missing API key on
openai.
Commands and Re-Authentication¶
- [ ] Decide whether this issue includes logout.
- [ ] If included, add
/logoutor/auth logoutcommand. - [ ] Add re-authentication path after refresh failure.
- [ ] Add user-facing message for expired or revoked credentials.
- [ ] Add tests for credential removal if logout is included.
Documentation¶
- [ ] Update README or user docs with
Codex (ChatGPT OAuth)setup. - [ ] Document that headless login is not supported.
- [ ] Document that API-key OpenAI remains available.
- [ ] Document where credentials are stored.
- [ ] Document how to reset credentials manually.
Verification¶
- [ ] Run
go mod tidy. - [ ] Run
go test ./.... - [ ] Manually test first-time OAuth login.
- [ ] Manually test returning user flow.
- [ ] Manually test token refresh.
- [ ] Manually test
/modelswitching betweenopenaiandopenai-codex. - [ ] Manually verify no tokens are printed in logs or UI.
- [ ] Manually verify auth file permissions.