Skip to content

Codex sign-in parity: expose + explain ChatGPT-subscription vs OpenAI-API-key, the way Claude already does #732

Description

@serge-ivo

Problem

The engine editor (agents/coder/web/src/EnginesModal.tsx) makes Claude's billing crystal clear — Auto / Machine login / Subscription token (Claude Code key) / Anthropic API key (per-token billing), with continuity/metering/attribution notes. But the subscription option is hardcoded Claude-only:

  • UI: {isClaude && <option value="subscription">…}
  • resolveEngineEnv (workers/api/src/lib/coding-engines.ts): if (session.clientType !== "claude") return … machine login — "Only Claude has a subscription token env; for other engines these modes mean the machine login."

So for Codex the user only gets Auto (this machine's login), Machine login only, and OpenAI API key (per-token billing). That's wrong: Codex supports Sign in with ChatGPT (codex login, a Plus/Pro/Team plan — no API key, no per-token charge), the direct analogue of Claude's subscription. Today a ChatGPT subscriber is forced into either:

The single distinction the product exists to make clear — API key vs subscription — is exactly the one missing for Codex.

Mechanic (why it's implementable, and how it differs from Claude)

Claude injects a token: resolveEngineEnv sets CLAUDE_CODE_OAUTH_TOKEN and strips ANTHROPIC_API_KEY. Codex has no injectable subscription token — its ChatGPT auth lives in ~/.codex/auth.json on the runner from codex login. So for Codex, "subscription" is the mirror image minus the injection:

  • Codex subscription mode = strip OPENAI_API_KEY (empty-value-means-remove, already supported by the runner's mergeEnv) so the CLI falls back to its own codex login ChatGPT session instead of an inherited/vault key. This is the same move Claude's subscription mode makes to stop an inherited key from silently forcing per-token billing.
  • api-key mode stays as-is (inject OPENAI_API_KEY from the vault, per-token).

Proposed fix

  1. Generalize EngineAuth beyond Claude. Let subscription apply to Codex; resolveEngineEnv for Codex-subscription strips the provider key (uses codex login). Keep the type the same (auto | machine | subscription | api-key); just stop gating subscription on clientType === "claude".
  2. Per-engine copy that names the real mechanism (no generic labels):
    • Claude subscription → "Subscription token (from claude setup-token)"
    • Codex subscription → "ChatGPT subscription (from codex login) — no per-token charge, draws your ChatGPT plan"
    • api-key (either) → "{Anthropic|OpenAI} API key — billed per token to your own account"
    • machine → say concretely what that login IS per engine (claude.ai keychain session / codex login / OpenAI key configured in the CLI) and that its spend is unattributable.
    • auto for Codex → "ChatGPT login if present, else whatever the CLI has" instead of the current bare "this machine's login".
  3. Attribution note (engine-attribution-note) covers Codex, mirroring the Claude hasClaudeCodeToken logic: read whether a usable Codex login is present and state the payer outcome (subscription = drawn from plan, unattributed at token level but not per-token-billed vs api-key = per-token to OpenAI).
  4. Symmetric "what this costs" line shown for both engines so API-key-vs-subscription is legible at a glance, not inferred from an option label.

Acceptance

  • Codex presets offer a "ChatGPT subscription" sign-in option; selecting it makes the runner spawn Codex with OPENAI_API_KEY stripped (verify via the engine-auth report: resolved != api-key).
  • Each engine's sign-in options and notes name the concrete credential (claude setup-token / codex login / provider API key) and its billing consequence in plain words.
  • engineAuthReport/engineAuthWarning produce correct asked-vs-got warnings for Codex (e.g. "you chose ChatGPT subscription but an OpenAI key reached the engine → billing per token").
  • Existing Claude behaviour is unchanged (regression-pinned).

Refs: #551, #556, #731 (invocation mode is the sibling per-preset dimension), #728

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2: correctnessReal defect, no live harm today — inert fields, miscounts, missing guardscoderThe Coder wedge agent (#68) — Engine, Pilot, Co-pilot, Loop, OverseerenhancementNew feature or requestfrontendFrontend / UI workrunnerpackages/browser-runner + packages/cli — ships only via an npm publish

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions