Skip to content

fix(ai): scope OpenAI Codex credential identity by ChatGPT workspace#6095

Merged
can1357 merged 3 commits into
can1357:mainfrom
will-bogusz:fix/codex-workspace-scoped-credentials
Jul 20, 2026
Merged

fix(ai): scope OpenAI Codex credential identity by ChatGPT workspace#6095
can1357 merged 3 commits into
can1357:mainfrom
will-bogusz:fix/codex-workspace-scoped-credentials

Conversation

@will-bogusz

@will-bogusz will-bogusz commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Important

This PR was auto-closed by the vouch bot before the branch advanced, so the diff shown above is frozen at the original commit and is stale. The current, slimmer version lives on the same branch — see the live compare (6 files: ~75-line core diff + a curated regression suite). Vouch request: #6097. The description below matches the current branch.

What

One ChatGPT email can hold several workspaces — a personal Plus/Pro plan plus Team/Enterprise seats, each with its own workspace-scoped OAuth token and independent 5h/weekly limit pools. omp keys openai-codex credentials by bare email, so it can only hold one of them at a time. This makes the ChatGPT workspace (chatgpt_account_id) part of the credential identity by routing it through the org-scoped identity machinery that #5170 shipped for Anthropic. It is the openai-codex half of #2966 (the Anthropic half is live and verified there); it also covers the #633 scenario.

Why

Logging into the second workspace silently replaces the first — the upsert matches on email:<same>, so multi-account rotation never sees two accounts and one seat's quota pool is wasted (disabled_cause: "replaced by newer credential").

How

  • oauth/openai-codex.ts: login captures orgId = chatgpt_account_id and orgName = chatgpt_plan_type (access-token claim, id_token fallback) on the returned credentials; refresh deliberately returns no org fields so merge sites preserve the stored value verbatim.
  • auth-storage.ts: openai-codex folds into the org-composed identity branch (email:<e>|org:<ws>); the codex email short-circuit is removed. Legacy bare-email rows are claimed one-way by the first org-scoped login, mirroring the Anthropic migration. Report-to-credential block reconciliation is tightened so every identity dimension present on both sides must agree — the workspace id is shared by every member, so a single-dimension match can cross-link siblings.
  • One subtle rule: a base identifier that merely repeats the org qualifier's id (codex stores the workspace id as both accountId and orgId) carries no per-user identity and is excluded from replacement claims, so one member's login can never re-key another member's same-org row. Provably inert for Anthropic (account UUID ≠ org UUID).
  • Usage reports need no codex-specific stamp: the provider-neutral attach path in AuthStorage already copies orgId/orgName from the fetching credential onto the report, and the org-partitioned dedupe branch now covers both providers.

Verification

  • All existing identity/dedupe/usage regression suites green, including every openai-codex collapses distinct emails that share account_id in auth dedupe and /usage #197/fix auth-storage identity regression for Codex and Anthropic OAuth dedupe #343/fix(ai): restore email-first identity matching for Codex and Anthropic #344 pin unchanged (org-less fixtures resolve to byte-identical keys — legacy stores don't re-key until a new login): 152 tests across the 7 ai suites, 36 more across the coding-agent blast radius.

  • New regression suite (auth-storage-codex-workspace-identity.test.ts) pins the workspace-identity invariants: same email + different workspace coexists, same-workspace re-login replaces in place, two members of one workspace stay distinct, legacy bare-email rows are claimed by the first workspace-scoped login and never clobbered by workspace-less credentials, and usage reports partition per workspace through the provider-neutral metadata attach path (no codex-specific stamp).

  • bun check fully clean.

  • Live two-workspace E2E against the real backend (sandboxed agent dir, browser OAuth, one email holding a personal Pro plan and a Team seat): both logins produced coexisting rows keyed email:<e>|org:<ws> with orgId byte-identical to each token's chatgpt_account_id and orgName from chatgpt_plan_type ("pro"/"team"), and omp usage reports both accounts with independent pools:

    Openai Codex — 2 accounts
      ● <email> · pro  · plan: pro    ███ 24.0% used
      ● <email> · team · plan: team   ░░░  0.0% used
      capacity: 7d → 0.24/2 accounts used (1.76× quota left)
    

One ChatGPT email can hold several workspaces (a personal Plus/Pro plan
plus Team/Enterprise seats), each with its own workspace-scoped OAuth
token and independent limit pools. Codex credentials were deduped by
bare email, so logging into the second workspace silently replaced the
first, and usage reports from the two pools merged into one row.

- capture the workspace (chatgpt_account_id) as orgId at login, with
  the plan type as its display label; token refreshes never rewrite it
- key openai-codex credential identity as email + org via the existing
  org-scoped machinery; legacy email-keyed rows are claimed in place by
  the first workspace-scoped login, and workspace-less credentials
  never clobber workspace-scoped rows
- exclude the org-mirroring account base from same-org row claims so
  two members of one workspace (shared chatgpt_account_id) keep
  separate rows
- partition usage-report dedupe by workspace and require every shared
  identity dimension to agree when reconciling codex usage blocks

Fixes the openai-codex half of can1357#2966 (anthropic half shipped in can1357#5170);
also covers the can1357#633 scenario.
@github-actions

Copy link
Copy Markdown
Contributor

Hi @will-bogusz, thanks for your interest in contributing!

This project requires that pull request authors are vouched, and you are not in the list of vouched users.

This PR will be closed automatically. See https://github.com/can1357/oh-my-pi/blob/main/CONTRIBUTING.md for more details.

@github-actions github-actions Bot closed this Jul 20, 2026
Drop the codex usage-provider orgId stamp (the provider-neutral report
attach path in AuthStorage fetchUsage already stamps metadata.orgId and
orgName from an org-carrying credential), drop the test-only fetchImpl
seam on refreshOpenAICodexToken plus its dedicated suites in favor of the
existing can1357#197/can1357#343/can1357#344 regression pins, and compress commentary to match
the surrounding Anthropic patterns.
Restore a curated regression suite for the workspace identity change:
same email across workspaces coexists, same-workspace re-login replaces
in place, two members of one workspace stay distinct, legacy bare-email
rows are claimed by the first workspace-scoped login and never clobbered
by workspace-less credentials, and usage reports partition per workspace
via the provider-neutral metadata attach path (no codex-specific stamp).
@can1357 can1357 reopened this Jul 20, 2026
@can1357
can1357 merged commit 924ea9a into can1357:main Jul 20, 2026
1 check passed
@github-actions github-actions Bot added the vouched Passed the vouch gate label Jul 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

vouched Passed the vouch gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants