Skip to content

fix(codex): stop pool accounts from spending the native main account (#7398) - #7414

Merged
lidge-jun merged 8 commits into
devfrom
fix/7398-account-pool-usage
Oct 19, 2026
Merged

lidge-jun merged 8 commits into
devfrom
fix/7398-account-pool-usage

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

Summary

Closes #7398. Builds on #7399 by @terrytan95, preserving both original commits/authorship; the additional commits cover already-stored duplicates and native relogin.

  • Root cause A: src/codex/auth-collision.ts:100 previously checked only pool rows, allowing a clone of the native main login to masquerade as an alternate quota account.
  • Root cause B: src/oauth/chatgpt-shared.ts:44 omitted the namespaced profile email; src/codex/auth-api/login-flow.ts:266 substituted a local label before identity checks and lost the JWT plan when WHAM failed. Keep identity evidence until admission, then apply the display fallback.
  • Existing duplicates are excluded from rotation, pinning, exact stored-account routing and context revalidation. They remain stored. account-list.ts:155 exposes selectionExcludedReason: "duplicate_main" and selectionExcludedAction in the dashboard DTO. No GUI layout changes.
  • Owned lifecycle observations publish a memory-only identity snapshot scoped to home/generation. Native relogin, same-ID member/plan changes, and confirmed API-key-only login update/retire it; malformed reads retain verified evidence. External relogins take effect at the next owned observation. Caller-owned requests and passive lists never refresh this snapshot by opening native credentials.
  • OAuth admission now holds native-main admission and a shared claim for the bounded pinned-path read. Busy/unreadable identity gives an actionable retry-login refusal. A duplicate appearing during token refresh is a policy refusal, never a reason to quarantine valid credentials.

Decisions and security review

  1. C3 task, security-depth review. Missing email cannot prove a distinct member: same ChatGPT account ID + same personal/workspace bucket + equal or missing email is conservatively excluded. Known different members, workspace IDs and plan buckets remain independent. Legacy local-ID email placeholders use token metadata.

  2. Item 5: yes, a manual Pool preference can still use caller main. It is not an exclusive binding. Four synthetic tests prove preference/excluded/cooldown paths may select caller main, exact binding refuses, and an observed paused main refuses. Preserve this established policy; Clarify manual pool pin versus caller-main fallback and consider pool-only policy #7410 tracks product policy/UX. Successful caller-main selection/fallback emits static reason-only diagnostics, with no account IDs, emails or credentials.

  3. Independent review repairs. Sol found unowned native reads and a read-fence exemption in the first draft, unowned OAuth reads in fix(codex): prevent duplicate pool registration of native main account and fix access token email extraction #7399, refresh-time reauth poisoning, and stale exclusion after API-key-only native login. All accepted and repaired with regressions. Final independent static review: PASS at e2ab0d778d4f8ebb270f0dd4f79c2c81c27c309f; reviewer tests NOT RUN, coordinator tests below. Argon was available in the catalog but failed at runtime with 403 (Argon delegated audit terminates with provider 403 on local Responses endpoint #7411); it supplies no approval evidence.

  4. Scope and ownership. No real credential reads, user settings changes, new dependency declarations, account deletion, proxy/app shutdown, force push, direct dev push or merge. Original author credit retained. This PR does not modify/comment on/close fix(codex): prevent duplicate pool registration of native main account and fix access token email extraction #7399.

  5. Validation constraints. Preservation fixtures now represent a legitimate separate plan bucket, retaining their policy assertions. Layout registrations use a shard rather than raising size limits. Bun 1.4.3 is a scratch-local test runner; no global installation/config changes.

  6. Review-ready disposition. Exact-head hosted CI and independent static security review passed, and all 565 focused regressions passed. The five macOS changed-mode timeouts remain recorded in Service update recovery tests exceed default 5-second timeout on macOS #7417/Messages ingress alternate-main success-control times out in changed-mode suite #7418; this is not a claim that the full local suite passed. Marked ready for maintainer review, with no merge or approval bypass.

Issue ledger

Verification

Base: eb4bf967001327624acca1341a9a51aba0a3ca05; final local head: e2ab0d778d4f8ebb270f0dd4f79c2c81c27c309f.

All behavioral tests use isolated temporary homes and fake credentials/JWTs. No live inference.

Check Fresh evidence
Admission/email repro before #7399 381 pass / 10 fail; after credited patch 391 pass / 0 fail
Existing/reverse duplicate repro on dev 4 pass / 11 fail; final file 30 pass / 0 fail
Fenced OAuth admission repro 0 pass / 1 fail before repair; now included in passing admission suite
Refresh-time collision repro 1 regression failed before repair; passing after policy-error correction
API-key-only transition repro 0 pass / 1 fail before repair; passing after classifier reuse
Required auth API/collision/OAuth suites plus auth-context, stored-main-duplicate, caller-fallback-policy, preserve-signed-in and core-lab-boundary Bun 1.4.3: 565 pass / 0 fail, 8 files, exit 0
bun run typecheck PASS, exit 0
bun run structure:check PASS, exit 0
bun run privacy:scan PASS, exit 0
bun test tests/test-layout*.test.ts tests/ci-workflows/file-size-ratchet.test.ts Bun 1.4.3: 36 pass / 0 fail, exit 0
docs-site frozen install + bun run build PASS, 585 pages, 83,065 internal links
bun run test:changed 35,252 pass / 43 skip / 5 timeout failures, 1,565 files, 415.6s, exit 1 at final head. Four service cases tracked in #7417; messages-ingress case tracked in #7418 (focused follow-up 6/6 PASS). No broad-suite PASS claimed. Earlier mixed-worktree run: 35,244 pass / 43 skip / 8 fail; not final certification
Full unfiltered suite NOT RUN separately: changed-mode already selects over 1,500 files / 35,000 cases; avoid duplicating broad resource use. Remaining platform coverage belongs to CI.
Required hosted CI PASS on exact head e2ab0d7: all four Ubuntu test shards, aggregate ci, gates, storage/API usage, keyring, Docker/npm smokes, docs/structure and hygiene completed successfully. CI run. macOS/Windows and other non-applicable jobs were SKIPPED per policy, not certified. Local timeout evidence remains explicit.

Focused final command: OCX_TEST_RUNNER_BUN=<scratch Bun 1.4.3> bun test tests/codex-integration/codex-auth-api.test.ts tests/codex-integration/codex-auth-collision.test.ts tests/oauth/chatgpt-oauth.test.ts tests/codex-integration/codex-auth-context.test.ts tests/codex-integration/codex-stored-main-duplicate.test.ts tests/codex-integration/codex-pool-caller-fallback-policy.test.ts tests/codex-integration/codex-preserve-signed-in.test.ts tests/lab/core-lab-boundary.test.ts (executed using the scratch 1.4.3 binary directly).

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

Co-authored-by: terrytan95 10609214+terrytan95@users.noreply.github.com

@coderabbitai

coderabbitai Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration
  • Configuration used: Repository: lidge-jun/opencodex/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 4f9547d7-52c5-45ef-a852-49b651715b82
📥 Commits

Reviewing files that changed from the base of the PR and between eb4bf96 and e2ab0d7.

📒 Files selected for processing: 24
  • docs-site/src/content/docs/guides/codex-integration.md
  • docs-site/src/content/docs/ko/guides/codex-integration.md
  • scripts/test-layout/layout-codex-collision.json
  • scripts/test-layout/layout.json
  • src/codex/account-lifecycle.ts
  • src/codex/account-usability.ts
  • src/codex/auth-api/account-list.ts
  • src/codex/auth-api/account-pause-group.ts
  • src/codex/auth-api/login-collision.ts
  • src/codex/auth-api/login-flow.ts
  • src/codex/auth-collision.ts
  • src/codex/auth-context.ts
  • src/codex/collision-identity.ts
  • src/oauth/chatgpt-shared.ts
  • structure/providers/openai-accounts.md
  • structure/providers/openai-signed-in-preservation.md
  • tests/codex-integration/codex-auth-api.test.ts
  • tests/codex-integration/codex-auth-collision.test.ts
  • tests/codex-integration/codex-pool-caller-fallback-policy.test.ts
  • tests/codex-integration/codex-preserve-signed-in.test.ts
  • tests/codex-integration/codex-stored-main-duplicate.test.ts
  • tests/fixtures/test-layout-expected.json
  • tests/helpers/codex-main-collision.ts
  • tests/oauth/chatgpt-oauth.test.ts
+-------------------------------------------------------------+
| All programming is an exercise in caching. - Terje Mathisen |
+-------------------------------------------------------------+
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

✅ Deterministic PR hygiene checks passed.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-19T23:35:14.317219Z e2ab0d7 Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@lidge-jun
lidge-jun merged commit 1202598 into dev Oct 19, 2026
43 of 45 checks passed
@lidge-jun
lidge-jun deleted the fix/7398-account-pool-usage branch October 19, 2026 23:34

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

Copy link
Copy Markdown

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: e2ab0d778d

ℹ️ 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".

export function reconcileMainCodexAccountRuntimeState(): boolean {
const read = readCodexTokensResult();
const read = readMainAuth();
if (read.status === "missing" || read.status === "api-key-only") observeMainCodexCollisionIdentity();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve collision evidence across a missing-file read

When auth.json is briefly absent during a non-atomic rewrite, this clears the observed main identity before the function's own unknown-identity branch preserves the remaining safety state. fetchMainAccountInfoWhileOwned explicitly treats a missing file as a possible rewrite gap (main-account-probe.ts:242-247), so a request arriving in that window can make the stored duplicate eligible and send its credential, defeating this change's protection until the main login is observed again. Only a positively identified API-key-only login or another confirmed identity transition should retire the collision observation; a single missing read should preserve it like other transient failures.

Useful? React with 👍 / 👎.

Comment on lines +154 to +156
...(duplicateMain ? {
selectionExcludedReason: "duplicate_main" as const,
selectionExcludedAction: CODEX_MAIN_DUPLICATE_ACTION,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Propagate duplicate-main exclusions to the account clients

For an existing stored duplicate, the server now returns this reason and action, but neither account-list consumer recognizes them: src/cli/account-api.ts:337,411-414 accepts and projects only plan_excluded, while gui/src/hooks/useCodexAccountPool.ts:94 and gui/src/components/codex-account-pool-cards.tsx:103-120 do the same. Consequently the CLI silently strips the exclusion even from JSON output, and the dashboard shows no reason or remediation—and may still display the unusable row as selected. Extend both client DTOs and renderers to preserve and present duplicate_main and its action.

Useful? React with 👍 / 👎.

terrytan95 added a commit to terrytan95/opencodex that referenced this pull request Oct 20, 2026
Preserve the implementation and regression coverage carried into dev by PR lidge-jun#7414, including the original PR lidge-jun#7399 fixes and its additional native-main admission and stored-duplicate protections. The resulting tree matches dev at 1202598.
x3M3x added a commit to x3M3x/opencodex that referenced this pull request Oct 24, 2026
…origin hints, store read sizing

- Move the three new test registrations from inline layout.json (2002 lines, over the 2000-line file-size ratchet per lidge-jun#7413) into scripts/test-layout/layout-persistent-dashboard.json, following the lidge-jun#7414 explicit-shard precedent.
- Clarify the remember-device hint: remembered sessions require a same-origin HTTPS dashboard (or loopback), all locales.
- readRecords sizes its buffer from the validated stat and rejects mid-read store growth instead of silently truncating at a fixed limit.
- structure/dashboard-and-usage.md: ordinary logout invalidates only the current browser session; remembered-device logout also forgets the device record and derived sessions.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants