Skip to content

Add multi-account support for Codex via OAuth token accounts#509

Open
niklassaers wants to merge 2 commits intosteipete:mainfrom
niklassaers:codex-multi-account
Open

Add multi-account support for Codex via OAuth token accounts#509
niklassaers wants to merge 2 commits intosteipete:mainfrom
niklassaers:codex-multi-account

Conversation

@niklassaers
Copy link

Summary

  • Add Codex to the token accounts system using OAuth access tokens from auth.json, following the existing Zai environment-variable injection pattern
  • New CodexSettingsReader reads CODEX_OAUTH_ACCESS_TOKEN from the environment (mirrors ZaiSettingsReader)
  • CodexOAuthFetchStrategy resolves credentials from env override first, falling back to ~/.codex/auth.json
  • "Import from auth.json" button in Providers settings extracts the email from the JWT id_token for labeling
  • "Add Account..." menu action opens Settings > Providers directly
  • Fix keyboard focus in Settings window for LSUIElement menu bar apps (retry activation with modern NSApp.activate() API on macOS 14+)

Approach

This uses .environment(key:) injection and routes through the OAuth fetch strategy (API-based), complementing #461 which uses .cookieHeader injection through the web dashboard strategy. The two approaches handle different auth flows and could coexist.

Files changed

File Change
TokenAccountSupportCatalog+Data.swift Add .codex catalog entry with env injection
CodexSettingsReader.swift New — env var reader for CODEX_OAUTH_ACCESS_TOKEN
ProviderTokenResolver.swift Add codexOAuthToken() / codexOAuthResolution()
CodexProviderDescriptor.swift Modify CodexOAuthFetchStrategy for env-first resolution
CodexProviderImplementation.swift Add loginMenuAction override ("Add Account...")
MenuDescriptor.swift / MenuContent.swift Add .settingsProvider action
StatusItemController+Actions.swift / +Menu.swift Wire up showSettingsProviders()
PreferencesProvidersPane.swift Wire up "Import from auth.json" with JWT email extraction
PreferencesProviderSettingsRows.swift Add "Import from auth.json" button
ProviderSettingsDescriptors.swift Add importFromFile to token accounts descriptor
HiddenWindowView.swift Fix keyboard focus with multi-retry activation

Test plan

  • Build: swift build compiles without errors
  • Existing tests: swift test passes
  • Open Preferences > Providers > Codex — verify "OAuth tokens" section appears
  • Click "Import from auth.json" — verify token is imported with email label
  • Add 2+ tokens, switch between them — verify usage fetches correctly
  • Enable "Show all token accounts" in Display — verify all accounts appear in menu
  • Click "Add Account..." in menu — verify it opens Settings > Providers
  • Verify keyboard input works in Settings text fields (Label, Token)

- Add Codex to TokenAccountSupportCatalog with environment-based injection
- Create CodexSettingsReader for CODEX_OAUTH_ACCESS_TOKEN env var
- Add codexOAuthToken/codexOAuthResolution to ProviderTokenResolver
- Modify CodexOAuthFetchStrategy to check env override before auth.json
- Add "Add Account..." menu action opening Settings → Providers
- Add settingsProvider menu action and routing
- Add "Import from auth.json" button in token accounts UI
- Force Settings window key status for proper keyboard focus
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e6acc3d0f1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


@MainActor
private static func forceSettingsWindowKey() {
NSApp.setActivationPolicy(.regular)

Choose a reason for hiding this comment

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

P2 Badge Restore accessory activation policy after focusing settings

Calling NSApp.setActivationPolicy(.regular) here permanently promotes this LSUIElement app to a regular app, and this commit never switches it back to .accessory; after opening Settings once, users can be left with a persistent Dock icon/app-switcher presence instead of menu-bar-only behavior. This side effect is triggered on the new settings-focus path and is likely unintended for a “no Dock icon” app.

Useful? React with 👍 / 👎.

Comment on lines +171 to +172
idToken: nil,
accountId: nil,

Choose a reason for hiding this comment

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

P2 Badge Carry account identity into env-based Codex OAuth fetches

When a token-account override is active, this path synthesizes CodexOAuthCredentials with idToken/accountId set to nil, so mapUsage cannot derive account email/plan from credentials. In the default single-account refresh flow (showAllTokenAccountsInMenu is false), we also skip the label-injection path, so switching Codex token accounts can show fallback/stale account identity in the menu even though usage is fetched with a different token.

Useful? React with 👍 / 👎.

…dentity

- Revert activation policy to .accessory when Settings window closes so
  the app does not permanently show in the Dock / Cmd-Tab switcher.
- Pass access token as idToken in env-based OAuth credentials so
  resolveAccountEmail can extract identity from the JWT claims.
- Improve keyboard focus: use multi-retry activation with modern
  NSApp.activate() API on macOS 14+, and set activation policy before
  posting the open-settings notification.
Copy link
Collaborator

@ratulsarna ratulsarna left a comment

Choose a reason for hiding this comment

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

Sharing a few questions I had while reading these changes.

-> (label: String, action: MenuDescriptor.MenuAction)?
{
guard TokenAccountSupportCatalog.support(for: .codex) != nil else { return nil }
return ("Add Account...", .settingsProvider)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could we walk through the first-time-user path here? If someone has no ~/.codex/auth.json yet, where in this flow do they actually run codex login?

title: "OAuth tokens",
subtitle: "Store Codex/OpenAI OAuth access tokens from auth.json.",
placeholder: "Paste access_token…",
injection: .environment(key: CodexSettingsReader.oauthAccessTokenKey),
Copy link
Collaborator

Choose a reason for hiding this comment

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

In Usage source = Auto, how do we ensure the selected Codex token account cannot end up showing usage fetched from a fallback CLI account when this OAuth token is stale or invalid?

guard let credentials = try? CodexOAuthCredentialsStore.load() else { return }
let email = Self.codexEmailFromCredentials(credentials)
let label = email ?? "Codex (\(DateFormatter.localizedString(from: Date(), dateStyle: .short, timeStyle: .none)))"
self.settings.addTokenAccount(provider: .codex, label: label, token: credentials.accessToken)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we double-check what auth behavior we want after import if we only store accessToken here? Where do refresh and account/workspace identity come from in that case?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants