Skip to content

feat(codex): add account-qualified model namespaces#426

Draft
chrisae9 wants to merge 16 commits into
lidge-jun:devfrom
chrisae9:feat/account-model-namespaces
Draft

feat(codex): add account-qualified model namespaces#426
chrisae9 wants to merge 16 commits into
lidge-jun:devfrom
chrisae9:feat/account-model-namespaces

Conversation

@chrisae9

@chrisae9 chrisae9 commented Jul 24, 2026

Copy link
Copy Markdown

Closes #425.

What this is for

OpenCodex can keep multiple Codex accounts authenticated, but a bare GPT entry does not say which account will serve the conversation. Pool mode may intentionally rotate or fail over, and Direct mode uses the caller/main login.

The missing workflow is: keep several accounts logged in, then explicitly choose one account for a conversation from the model picker without logging out or changing the active Pool account.

This is not a built-in “personal account / work account” feature. Those are possible user labels, nothing more. The feature works with arbitrary account records and user-owned selector names.

Behavior

This is opt-in from Codex Auth → Show each Codex account separately in the model picker.

When it is off:

  • the picker stays as it is today;
  • bare models such as gpt-5.6-sol keep normal Pool or Direct behavior;
  • existing qualified selections remain routable so saved conversations cannot silently fall through.

When it is on:

  • each native GPT model appears once for each available Codex account;
  • main/<model> selects the built-in Codex Desktop/main login;
  • each added account gets a public selector from its explicit alias or a random privacy-safe pXXXXXX label;
  • the public selector maps privately to the stored account ID; raw account IDs and emails are never copied into catalog slugs, provider log labels, or error text;
  • selecting a qualified model pins that request and conversation to the exact account;
  • quota balancing, transient failover, affinity rebinding, and unsupported-model account retry cannot switch it;
  • missing credentials, cooldown, or reauthentication fail closed;
  • the active Pool account is not changed.

Turning the setting off only hides the generated rows. It preserves custom selector mappings and saved qualified selections. Deleting an account hides its rows but retains the fail-closed binding; re-adding the same stored account restores it. Newly supported native GPT models receive the existing account selectors on the next catalog sync.

Qualified models still use the canonical openai Responses path, including HTTP/SSE, WebSocket, compact, native reasoning, tools, and service-tier metadata. Bare child/helper models keep Pool/Direct behavior; explicitly qualified helper models keep the exact account binding.

Configuration and compatibility

The UI manages these fields:

{
  "codexAccountPickerEnabled": true,
  "codexAccountNamespaces": {
    "main": "main",
    "side": "stored-account-id"
  }
}

The map keys are public selectors. The values are private local routing targets. Existing non-empty hand-written maps remain an explicit opt-in when the visibility override is omitted, and custom maps are preserved byte-for-byte across off/on toggles.

Fresh and ordinary existing configurations remain unchanged until the user enables the setting. Plain GPT IDs are not removed from routing, so existing threads and saved configuration retain their Pool/Direct semantics.

Account add/delete persists even if an immediate catalog refresh fails. OpenCodex retries once, reports a recoverable pending state, and shows an actionable ocx sync warning in both account-management surfaces.

Integration work

This branch merges current dev and preserves the completed account lifecycle, router, quota-probe, catalog, and response contracts that landed there.

Review fixes include:

  • privacy-safe public selectors that are independent of internal account IDs;
  • canonical-OpenAI admission and strict Direct/Pool fail-closed behavior;
  • reserved-ID, provider-prefix, and combo-alias collision handling;
  • routed-catalog outage preservation and stale-slug de-duplication;
  • account-aware multi-agent roster projection within the five-model client window;
  • serialized GUI setting writes and complete settings hydration;
  • recoverable catalog-refresh reporting for account creation and deletion;
  • synchronized documentation and localized UI copy.

Validation

  • Full isolated Bun suite: 4,280 tests across 327 files, 0 failures
  • TypeScript typecheck
  • GUI lint and production build
  • GUI interaction and controller tests
  • Privacy scan
  • Documentation build: 126 pages
  • Independent privacy, merge-contract, and final behavior reviews

Security review

This changes credential-to-model routing at a C4 authentication boundary. It still requires the explicit maintainer security review described in MAINTAINERS.md before merge.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds opt-in Codex account-qualified model namespaces such as personal/gpt-* and work/gpt-*. The change covers configuration, catalog generation, strict account routing, fail-closed behavior, management APIs, GUI settings, localization, tests, and documentation.

Changes

Codex account namespace foundation

Layer / File(s) Summary
Namespace contracts and validation
src/types.ts, src/codex/account-namespace*.ts, src/codex/account-namespaces.ts, src/config.ts, src/server/management/*, src/codex/account-lifecycle.ts
Adds namespace configuration, normalization and matching helpers, account-label generation, validation, provider/combo collision checks, account reservation rules, and binding cleanup on deletion.

Catalog and routing

Layer / File(s) Summary
Account-qualified catalog generation
src/codex/catalog/*, src/server/index.ts, tests/codex-catalog.test.ts, tests/claude-models-discovery.test.ts, tests/native-model-toggle.test.ts, tests/multi-agent-compat.test.ts, tests/combo-management-api.test.ts
Generates qualified native rows, applies visibility and priority rules, preserves subagent mappings, synchronizes stale entries, and exposes qualified models through discovery endpoints.
Fixed-account request routing
src/router.ts, src/codex/auth-context.ts, src/codex/routing.ts, src/server/responses/*, tests/router.test.ts, tests/codex-auth-context.test.ts, tests/server-auth.test.ts
Routes qualified selectors to one account, carries fixed-account metadata through responses, and prevents pool failover or account rebinding for those requests.

Settings and documentation

Layer / File(s) Summary
Settings API and account picker
src/server/management/config-routes.ts, src/codex/auth-api.ts, src/server/auth-cors.ts, gui/src/pages/CodexAuth.tsx, gui/src/components/CodexAccountPickerSetting.tsx, gui/src/i18n/*, tests/settings-stream-mode.test.ts, gui/tests/*
Adds enable/disable persistence, catalog refreshes, DTO state, localized picker UI, optimistic saving, feedback, and validation coverage.
Documentation and provider contract
docs-site/src/content/docs/guides/*, docs-site/src/content/docs/reference/configuration.md, structure/08_openai-provider-tiers.md
Documents configuration, routing precedence, picker rows, account binding, and failure semantics.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested reviewers: lidge-jun, wibias

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR implements the opt-in account-namespace picker, fixed-account routing, fail-closed behavior, and hidden-row preservation required by #425.
Out of Scope Changes check ✅ Passed All code, docs, and tests support the Codex account-namespace feature; no unrelated changes stand out.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding Codex account-qualified model namespaces.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@chrisae9
chrisae9 marked this pull request as ready for review July 25, 2026 03:33

@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: 374313200f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/server/management/config-routes.ts Outdated
Comment thread src/codex/catalog/sync.ts Outdated
Comment thread src/codex/account-namespaces.ts Outdated
Comment thread src/codex/catalog/sync.ts

@coderabbitai coderabbitai 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.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/codex/catalog/sync.ts (1)

87-138: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Exclusion-reason mismatch for bare model IDs when namespace clones fall outside the display window.

effectiveSubagentRoster's advertised/ordered.some check (Line 116, Line 122) now uses the new configuredSubagentModelMatchesEntry, which treats a bare configured slug as matching any of its account-bound clones. But the fallback lookup for exclusion diagnostics still uses the old configuredCatalogEntry (Line 123), which only does direct/slugsEquivalent matching — it will resolve a bare configured id straight to the bare native entry.

When codexAccountNamespaces is enabled, that bare native entry has visibility: "hide" (set in buildCatalogEntries, Lines 269-271). So if a model's account-bound clones exist but none made the top-MAX_SPAWN_AGENT_MODEL_OVERRIDES ordered slice (easily reachable once several native models × several namespaces produce more than 5 clones), the exclusion reason reported is "picker_hidden" (Line 127) instead of the more accurate "outside_display_limit" — even though the model is genuinely visible via its namespaced variants, just not within the priority window. This is untested: tests/multi-agent-compat.test.ts (Lines 161-176) only covers the case where a clone is inside the window.

🩹 Suggested direction
   const excluded = configured.flatMap((model): SubagentRosterExclusion[] => {
     if (ordered.some(({ entry }) => configuredSubagentModelMatchesEntry(model, entry))) return [];
-    const entry = configuredCatalogEntry(entries, model);
+    const entry = entries.find(candidate => configuredSubagentModelMatchesEntry(model, candidate))
+      ?? configuredCatalogEntry(entries, model);
     if (!entry) return [{ configured: model, reason: "missing_catalog_entry" }];

Based on the newly-added test coverage in tests/multi-agent-compat.test.ts only exercising the in-window case, this gap would benefit from a companion regression test simulating >5 competing clones.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/codex/catalog/sync.ts` around lines 87 - 138, Update
effectiveSubagentRoster and its fallback catalog lookup so bare configured model
IDs resolve against account-bound namespace clones using the same
configuredSubagentModelMatchesEntry semantics as the advertised/ordered checks.
When matching clones exist but fall outside MAX_SPAWN_AGENT_MODEL_OVERRIDES,
report outside_display_limit rather than picker_hidden. Add a regression test in
the multi-agent compatibility coverage with more than five competing clones.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs-site/src/content/docs/guides/codex-app-models.md`:
- Around line 27-54: The translated codex-app-models pages in ja, ko, ru, and
zh-cn are missing the English page’s account-specific model behavior. Update
each locale page to include the codexAccountNamespaces configuration,
exact-account model naming and selection, fail-closed behavior for missing or
unavailable credentials, bare-model Pool/Direct behavior, sync/catalog details,
and the corresponding configuration-reference link while preserving the existing
localized structure and translations.

In `@gui/src/pages/CodexAuth.tsx`:
- Around line 20-28: Update loadMode so it validates the fetch response before
applying codexAccountModeState or setNamespacesEnabled. Treat non-OK HTTP
responses as errors, preserving null or the last confirmed namespace state
instead of interpreting missing codexAccountNamespacesEnabled as disabled;
retain the existing namespaceMutationInFlightRef guard for successful responses.

In `@src/codex/auth-api.ts`:
- Around line 482-497: Add focused regression tests in
tests/codex-auth-api.test.ts for the account creation and deletion handlers in
the /api/codex-auth/accounts endpoint. Verify manual-import/OAuth creation
creates the expected namespace and triggers exactly one
refreshAccountNamespaceCatalog call, and deletion removes the namespace and
likewise triggers exactly one refresh. Cover both the POST flow around
appendDefaultCodexAccountNamespace and the DELETE flow around deleteCodexAccount
without changing unrelated /api/settings tests.

In `@src/codex/auth-context.ts`:
- Around line 100-101: Update the auth-context resolution flow so accountId is
validated before the Direct-mode shortcut, preventing Direct mode from bypassing
explicit account selection. Either resolve the requested account before Direct
handling or reject the accountId/Direct combination, preserving fail-closed
isolation; add a regression test covering this combination.

In `@src/config.ts`:
- Around line 623-634: Add regression tests for account-qualified combo aliases
at both affected sites: in src/config.ts lines 623-634, extend loadConfig()
coverage with a configured account namespace and assert a combo alias such as
work/gpt-5.6-sol invalidates the persisted configuration; in
src/server/management/combo-routes.ts lines 127-130, add a PUT /api/combos test
asserting the same alias returns 400 and config.combos remains unchanged.

In `@src/router.ts`:
- Around line 243-264: Update the namespace routing block around
codexAccountNamespaceEntries and routeResult to require the same canonical
OpenAI forward-provider check used for persisted codexAccountMode. Only inject
pool mode and codexAccountId when isCanonicalOpenAiForwardProvider confirms the
configured OPENAI_CODEX_PROVIDER_ID provider; otherwise fall through to normal
routing without Codex pool headers.

In `@structure/08_openai-provider-tiers.md`:
- Around line 67-76: Clarify the overview bullets in
structure/08_openai-provider-tiers.md to distinguish exact qualified account
binding for Responses/compact requests from global Pool/Direct behavior for
standalone image and alpha-search relays that do not carry the selected chat
model. Align the wording with the configuration reference and avoid implying
that all image, search, or vision surfaces preserve the selected account
namespace.

In `@tests/multi-agent-compat.test.ts`:
- Around line 161-176: Add a companion test for effectiveSubagentRoster covering
a bare configured model whose visible account-qualified replacements all fall
outside the top MAX_SPAWN_AGENT_MODEL_OVERRIDES priority window. Assert the
replacements are not advertised and that the excluded entries receive the
correct account-bound equivalence exclusion reason, using the existing
catalogFixture and V2 setup patterns.

---

Outside diff comments:
In `@src/codex/catalog/sync.ts`:
- Around line 87-138: Update effectiveSubagentRoster and its fallback catalog
lookup so bare configured model IDs resolve against account-bound namespace
clones using the same configuredSubagentModelMatchesEntry semantics as the
advertised/ordered checks. When matching clones exist but fall outside
MAX_SPAWN_AGENT_MODEL_OVERRIDES, report outside_display_limit rather than
picker_hidden. Add a regression test in the multi-agent compatibility coverage
with more than five competing clones.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6356e7a9-04a8-4b65-b641-96e11ba990c7

📥 Commits

Reviewing files that changed from the base of the PR and between dbed8c1 and 3743132.

📒 Files selected for processing (46)
  • docs-site/src/content/docs/guides/codex-app-models.md
  • docs-site/src/content/docs/guides/model-routing.md
  • docs-site/src/content/docs/reference/configuration.md
  • gui/src/components/CodexAccountPickerSetting.tsx
  • gui/src/i18n/de.ts
  • gui/src/i18n/en.ts
  • gui/src/i18n/ja.ts
  • gui/src/i18n/ko.ts
  • gui/src/i18n/ru.ts
  • gui/src/i18n/zh.ts
  • gui/src/pages/CodexAuth.tsx
  • gui/tests/codex-account-picker-setting.test.tsx
  • src/codex/account-lifecycle.ts
  • src/codex/account-namespace-match.ts
  • src/codex/account-namespaces.ts
  • src/codex/auth-api.ts
  • src/codex/auth-context.ts
  • src/codex/catalog/metadata.ts
  • src/codex/catalog/parsing.ts
  • src/codex/catalog/sync.ts
  • src/codex/routing.ts
  • src/config.ts
  • src/router.ts
  • src/server/auth-cors.ts
  • src/server/index.ts
  • src/server/management/combo-routes.ts
  • src/server/management/config-routes.ts
  • src/server/management/provider-routes.ts
  • src/server/responses/compact.ts
  • src/server/responses/core.ts
  • src/types.ts
  • structure/08_openai-provider-tiers.md
  • tests/claude-models-discovery.test.ts
  • tests/codex-auth-api.test.ts
  • tests/codex-auth-context.test.ts
  • tests/codex-catalog.test.ts
  • tests/codex-routing.test.ts
  • tests/codex-v2-gate.test.ts
  • tests/combo-management-api.test.ts
  • tests/config.test.ts
  • tests/management-provider-validation.test.ts
  • tests/multi-agent-compat.test.ts
  • tests/native-model-toggle.test.ts
  • tests/router.test.ts
  • tests/server-auth.test.ts
  • tests/settings-stream-mode.test.ts

Comment thread docs-site/src/content/docs/guides/codex-app-models.md Outdated
Comment thread gui/src/pages/CodexAuth.tsx
Comment thread src/codex/auth-api.ts Outdated
Comment thread src/codex/auth-context.ts
Comment thread src/config.ts
Comment thread src/router.ts
Comment thread structure/08_openai-provider-tiers.md
Comment thread tests/multi-agent-compat.test.ts Outdated
@lidge-jun

Copy link
Copy Markdown
Owner

🔒 Under maintainer review — detailed feedback incoming

@lidge-jun (maintainer) has this PR in an active review pass. Please do not merge or close
it
until the detailed review lands; a full comment with specific file:line findings,
failure modes, and suggested fixes is being prepared.

This PR is not part of the current integration batch. It is held back deliberately —
either because it touches a security boundary that MAINTAINERS.md requires an explicit
security review for, because it needs to be split into reviewable units, or because it needs
a rebase onto changes landing in dev right now.

Heads-up on rebasing: dev is receiving a coordinated integration batch today. If this PR
touches the same files, expect to rebase after that batch lands. The upcoming review comment
will name the exact overlapping paths so you only have to do it once.

No action needed from you until then. Thanks for the contribution and for your patience.

Review tracker: devlog/_plan/260725_pr_issue_rework · marker posted by the maintainer review pass

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@gui/src/i18n/ru.ts`:
- Around line 884-889: Update the Codex namespace visibility translations in
gui/src/i18n/ru.ts lines 884-889 and gui/src/i18n/zh.ts lines 602-607: describe
visibility for each configured Codex login rather than only added accounts, and
revise the save confirmation to state that account display settings were updated
rather than implying the selected or primary account changed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b16b6e3c-a687-4f49-acb2-15709ae3202c

📥 Commits

Reviewing files that changed from the base of the PR and between 3743132 and 0dda15e.

📒 Files selected for processing (7)
  • gui/src/i18n/de.ts
  • gui/src/i18n/en.ts
  • gui/src/i18n/ja.ts
  • gui/src/i18n/ko.ts
  • gui/src/i18n/ru.ts
  • gui/src/i18n/zh.ts
  • gui/tests/codex-account-picker-setting.test.tsx

Comment thread gui/src/i18n/ru.ts Outdated
@chrisae9 chrisae9 changed the title Add account-qualified Codex model namespaces feat(codex): add account-qualified model namespaces Jul 25, 2026
@github-actions github-actions Bot added the enhancement New feature or request label Jul 25, 2026

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/codex/auth-api.ts (1)

427-434: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Do not silently discard catalog-refresh failures.

Lines 429-434 convert every refreshCodexModelCatalog failure into a successful account mutation. A failed refresh leaves the injected picker catalog stale: new account-qualified models may never appear, and deleted-account selectors can remain visible. Ignore only the expected “catalog not injected” case; otherwise log and enqueue/retry a refresh (or return a recoverable error state to the management client).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/codex/auth-api.ts` around lines 427 - 434, Update
refreshAccountNamespaceCatalog so it does not silently swallow
refreshCodexModelCatalog failures: ignore only the expected
missing-catalog-injection case, while logging other failures and enqueueing or
retrying a catalog refresh, or returning a recoverable error state to the
management client. Preserve successful account persistence and ensure the
injected picker catalog is eventually refreshed after account changes.
gui/src/pages/CodexAuth.tsx (1)

40-45: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Use the in-flight ref to actually serialize saves.

namespaceSaving does not update synchronously, so rapid clicks can start multiple PUTs and catalog refreshes before rerender. Include namespaceMutationInFlightRef.current in the guard.

Proposed fix
-    if (namespaceSaving || namespacesEnabled === null || namespacesEnabled === next) return;
+    if (namespaceMutationInFlightRef.current || namespaceSaving
+      || namespacesEnabled === null || namespacesEnabled === next) return;

As per path instructions, “GUI state changes stay consistent with the management API responses.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gui/src/pages/CodexAuth.tsx` around lines 40 - 45, Update the
saveNamespacesEnabled guard to also return when
namespaceMutationInFlightRef.current is true, before setting the ref or starting
the PUT/refresh flow. Keep the existing namespaceSaving, null-state, and
unchanged-value checks so rapid clicks are serialized and GUI state remains
consistent with management API responses.

Source: Path instructions

src/codex/catalog/sync.ts (1)

567-583: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Preserve routed entries when provider discovery fails with namespaces enabled.

mergeCatalogEntriesForSync treats routedEntries.length === 0 as the fetch-failure preservation signal (Line 449), but Line 567 appends generated account-bound native rows before passing them as that parameter. Once namespaces are enabled, goEntries is non-empty even when gatherRoutedModels() returned no routed models, so existing entries for configured providers are removed instead of preserved during a transient discovery failure.

Keep routed-discovery results separate from account-bound entries when calling the merge function, and append account-bound rows only after the preservation decision. Add a regression test with an empty routed fetch plus an existing configured-provider catalog row.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/codex/catalog/sync.ts` around lines 567 - 583, Preserve the distinction
between routed discovery results and generated account-bound rows in the sync
flow around goEntries and mergeCatalogEntriesForSync. Pass the actual routed
discovery result to the merge function so an empty routed fetch triggers
configured-provider preservation, then append account-bound entries after that
decision. Add a regression test covering an empty routed fetch with an existing
configured-provider catalog row.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs-site/src/content/docs/reference/configuration.md`:
- Around line 55-56: Update the codexAccountNamespaces description in the
configuration reference to say account-qualified native rows replace plain GPT
rows when the namespace map is non-empty, not merely when the setting is
present. Preserve the existing behavior and clarify that an empty/default {} map
does not enable account-qualified rows or hide bare rows.

In `@docs-site/src/content/docs/zh-cn/guides/codex-app-models.md`:
- Around line 26-28: Restore the paragraph flow in the Chinese documentation by
completing the deletion and re-addition behavior text before the “手动配置与 `ocx
sync` 详见[配置参考]” reference link. Remove the interrupted and duplicate reference
sentence, preserving the intended account-binding behavior description and
keeping the translated paragraph readable.

In `@src/server/management/config-routes.ts`:
- Around line 199-210: Update the request-body parsing in the settings route
before the existing field checks: parse the JSON result as unknown, require
isPlainRecord(raw), and return the existing 400 invalid-body response for null
or any non-object body. Only access codexAutoStart, streamMode, and
codexAccountPickerEnabled after this validation, preserving the current
supported-setting and type checks for valid records.

---

Outside diff comments:
In `@gui/src/pages/CodexAuth.tsx`:
- Around line 40-45: Update the saveNamespacesEnabled guard to also return when
namespaceMutationInFlightRef.current is true, before setting the ref or starting
the PUT/refresh flow. Keep the existing namespaceSaving, null-state, and
unchanged-value checks so rapid clicks are serialized and GUI state remains
consistent with management API responses.

In `@src/codex/auth-api.ts`:
- Around line 427-434: Update refreshAccountNamespaceCatalog so it does not
silently swallow refreshCodexModelCatalog failures: ignore only the expected
missing-catalog-injection case, while logging other failures and enqueueing or
retrying a catalog refresh, or returning a recoverable error state to the
management client. Preserve successful account persistence and ensure the
injected picker catalog is eventually refreshed after account changes.

In `@src/codex/catalog/sync.ts`:
- Around line 567-583: Preserve the distinction between routed discovery results
and generated account-bound rows in the sync flow around goEntries and
mergeCatalogEntriesForSync. Pass the actual routed discovery result to the merge
function so an empty routed fetch triggers configured-provider preservation,
then append account-bound entries after that decision. Add a regression test
covering an empty routed fetch with an existing configured-provider catalog row.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 32144e30-0643-4cc7-a46b-5b63ec75f575

📥 Commits

Reviewing files that changed from the base of the PR and between 0dda15e and c4e3496.

📒 Files selected for processing (40)
  • docs-site/src/content/docs/guides/codex-app-models.md
  • docs-site/src/content/docs/guides/model-routing.md
  • docs-site/src/content/docs/ja/guides/codex-app-models.md
  • docs-site/src/content/docs/ko/guides/codex-app-models.md
  • docs-site/src/content/docs/reference/configuration.md
  • docs-site/src/content/docs/ru/guides/codex-app-models.md
  • docs-site/src/content/docs/zh-cn/guides/codex-app-models.md
  • gui/src/i18n/de.ts
  • gui/src/i18n/en.ts
  • gui/src/i18n/ja.ts
  • gui/src/i18n/ko.ts
  • gui/src/i18n/ru.ts
  • gui/src/i18n/zh.ts
  • gui/src/pages/CodexAuth.tsx
  • gui/tests/codex-account-picker-setting.test.tsx
  • src/codex/account-lifecycle.ts
  • src/codex/account-namespaces.ts
  • src/codex/auth-api.ts
  • src/codex/auth-context.ts
  • src/codex/catalog/sync.ts
  • src/config.ts
  • src/router.ts
  • src/server/auth-cors.ts
  • src/server/index.ts
  • src/server/management/config-routes.ts
  • src/types.ts
  • structure/08_openai-provider-tiers.md
  • tests/claude-models-discovery.test.ts
  • tests/codex-auth-api.test.ts
  • tests/codex-auth-context.test.ts
  • tests/codex-catalog.test.ts
  • tests/codex-v2-gate.test.ts
  • tests/combo-management-api.test.ts
  • tests/config.test.ts
  • tests/management-provider-validation.test.ts
  • tests/multi-agent-compat.test.ts
  • tests/native-model-toggle.test.ts
  • tests/router.test.ts
  • tests/server-auth.test.ts
  • tests/settings-stream-mode.test.ts

Comment thread docs-site/src/content/docs/reference/configuration.md Outdated
Comment thread docs-site/src/content/docs/zh-cn/guides/codex-app-models.md Outdated
Comment thread src/server/management/config-routes.ts Outdated

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
docs-site/src/content/docs/reference/configuration.md (1)

120-123: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Document the canonical openai route for qualified selectors.

These lines explain account pinning and the bypassed Pool behaviors, but do not state that selectors such as main/<model> and side/<model> still route through the canonical openai provider. Without that clarification, readers may interpret them as generic provider-qualified routes or as selectors subject to Pool/Direct switching.

Add that qualified native selectors use openai, pin the exact Codex account, and never fall through to Pool/Direct selection.

As per path instructions, account-qualified native selectors must be documented as canonical openai routes with fixed account binding.

Proposed wording
 These selectors do not change `activeCodexAccountId`. They bypass quota auto-switch, transient-
 failure failover, affinity rebinding, and unsupported-model retry. If the exact account is missing,
 cooling down, or needs reauthentication, the request fails instead of using another account. Bare
 `gpt-*` models keep the configured Pool/Direct behavior.
+Qualified native selectors still route through the canonical `openai` provider and pin the request
+to the selected Codex account; they never fall through to Pool/Direct account selection.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs-site/src/content/docs/reference/configuration.md` around lines 120 -
123, Update the qualified-selector documentation near the account-pinning
behavior to state that selectors such as main/<model> and side/<model> use the
canonical openai route, bind to the exact Codex account, and never fall through
to Pool/Direct selection. Preserve the existing descriptions of bypassed
behaviors and failure handling.

Source: Path instructions

src/server/management/config-routes.ts (2)

227-240: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Refresh the catalog when initializing bindings on an already-enabled picker.

pickerWasEnabled is captured before defaultCodexAccountNamespaces(config) populates an empty map. If persisted configuration has codexAccountPickerEnabled: true but no namespaces, this request adds bindings while pickerWasEnabled === pickerIsEnabled, so refreshCodexCatalogBestEffort() is skipped and the picker remains stale until a later sync.

Track whether bindings were initialized/changed and include that in the refresh condition. Add a regression case to tests/settings-stream-mode.test.ts for an already-enabled picker with an empty namespace map.

Proposed fix
+    const namespacesWereEmpty = Object.keys(config.codexAccountNamespaces ?? {}).length === 0;
     const pickerWasEnabled = codexAccountPickerIsEnabled(config);
     if (body.codexAccountPickerEnabled === true) {
-      if (Object.keys(config.codexAccountNamespaces ?? {}).length === 0) {
+      if (namespacesWereEmpty) {
         config.codexAccountNamespaces = defaultCodexAccountNamespaces(config);
       }
       config.codexAccountPickerEnabled = true;
@@
     }
     const pickerIsEnabled = codexAccountPickerIsEnabled(config);
+    const namespacesWereInitialized =
+      namespacesWereEmpty && Object.keys(config.codexAccountNamespaces ?? {}).length > 0;
     saveConfig(config);
-    if (pickerWasEnabled !== pickerIsEnabled) {
+    if (pickerWasEnabled !== pickerIsEnabled || namespacesWereInitialized) {
       await refreshCodexCatalogBestEffort();
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/management/config-routes.ts` around lines 227 - 240, Update the
picker configuration flow around codexAccountPickerIsEnabled and
defaultCodexAccountNamespaces to track whether empty namespace bindings were
initialized. Include that binding-change state in the condition that calls
refreshCodexCatalogBestEffort, while preserving the existing enabled-state
transition behavior. Add a regression case in settings-stream-mode.test.ts
covering an already-enabled picker with an empty namespace map.

242-246: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Expose codexAccountPickerEnabled in GET /api/settings. The PUT payload already returns this field, but the GET DTO still omits it, so clients hydrating settings can see undefined and render a stale toggle after the persisted value changes. Add the effective value to the GET response and a regression test for the GET shape in tests/settings-stream-mode.test.ts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/management/config-routes.ts` around lines 242 - 246, Update the
GET settings response in the relevant config-routes handler to include the
effective codexAccountPickerEnabled value, matching the PUT response and
persisted configuration behavior. Add a regression test in
settings-stream-mode.test.ts that verifies the GET /api/settings DTO contains
this field and its current value.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@docs-site/src/content/docs/reference/configuration.md`:
- Around line 120-123: Update the qualified-selector documentation near the
account-pinning behavior to state that selectors such as main/<model> and
side/<model> use the canonical openai route, bind to the exact Codex account,
and never fall through to Pool/Direct selection. Preserve the existing
descriptions of bypassed behaviors and failure handling.

In `@src/server/management/config-routes.ts`:
- Around line 227-240: Update the picker configuration flow around
codexAccountPickerIsEnabled and defaultCodexAccountNamespaces to track whether
empty namespace bindings were initialized. Include that binding-change state in
the condition that calls refreshCodexCatalogBestEffort, while preserving the
existing enabled-state transition behavior. Add a regression case in
settings-stream-mode.test.ts covering an already-enabled picker with an empty
namespace map.
- Around line 242-246: Update the GET settings response in the relevant
config-routes handler to include the effective codexAccountPickerEnabled value,
matching the PUT response and persisted configuration behavior. Add a regression
test in settings-stream-mode.test.ts that verifies the GET /api/settings DTO
contains this field and its current value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 98cb4229-e2a7-4e27-ac1a-3b296db8c8f1

📥 Commits

Reviewing files that changed from the base of the PR and between c4e3496 and 2ff3e24.

📒 Files selected for processing (4)
  • docs-site/src/content/docs/reference/configuration.md
  • docs-site/src/content/docs/zh-cn/guides/codex-app-models.md
  • src/server/management/config-routes.ts
  • tests/settings-stream-mode.test.ts

@lidge-jun

Copy link
Copy Markdown
Owner

Review deferred — head moved during the pass

Thanks for your patience. This PR was queued for a detailed review, but the head advanced from 37431320 to 2ff3e24b (now 50 files, +2111/-74) while the pass was running. Posting a review written against the older diff would mean commenting on line references that have since moved, so it is being re-audited against the current head instead rather than shipped stale.

One finding worth flagging now, since it appears to survive the newer commits: src/codex/account-namespaces.ts derives the public namespace from account.id whenever an account has no alias, and that namespace then reaches catalog model slugs and provider log labels. An account imported manually or created through OAuth without an alias would expose its internal identifier through the model catalog and logs — which contradicts this PR's own stated contract that account ids stay server-side, and conflicts with the repository rule against logging account identifiers. Please generate a stable non-PII label instead and keep catalog/error/log serialization limited to it.

Also note: dev moved to ebc62d1f today. It now includes #370 in src/codex/auth-api.ts, src/codex/auth-context.ts, and src/codex/account-lifecycle.ts, #389 in src/router.ts, and #439 in src/types.ts — all files this PR touches, and GitHub currently reports the branch as CONFLICTING. Please re-cut against those completed contracts rather than restoring the pre-integration code.

Because this changes account selection, OAuth-created credential lifecycle, and credential-to-model routing, it remains a C4 authentication boundary requiring the explicit maintainer security review that MAINTAINERS.md mandates. The full review against the current head will follow.

# Conflicts:
#	src/codex/auth-api.ts
#	src/codex/auth-context.ts
#	src/codex/routing.ts
#	src/router.ts
#	src/server/responses/compact.ts
#	src/server/responses/core.ts
@Wibias
Wibias marked this pull request as draft July 25, 2026 19:44
@chrisae9

Copy link
Copy Markdown
Author

Updated the branch after merging current dev (6f44caf8) and pushed the review fixes in 897d2660.

The maintainer privacy finding is fixed at the boundary: picker selectors now come from an explicit alias or persisted random pXXXXXX label. Legacy accounts without a stored label get independent randomness; the ID-derived diagnostic fallback is never used as a public selector. Raw account IDs/emails do not enter catalog slugs, provider log labels, or request errors.

I also addressed the remaining review findings:

  • preserved routed catalog rows during discovery outages and removed stale slug collisions;
  • kept exact-account routing fail-closed across Direct/Pool, cooldown probes, retries, WebSocket, and compact;
  • enforced reserved-ID, provider-prefix, combo-alias, and canonical-openai guards;
  • preserved account-qualified multi-agent candidates inside the client display window;
  • serialized GUI writes, hydrated the effective setting, and surfaced recoverable catalog-refresh failures in both account-management surfaces;
  • synchronized the localized UI and docs around generic multi-account targeting rather than personal/work roles.

Validation on the final head: 4,280 tests across 327 files, 0 failures; typecheck, GUI lint/build, privacy scan, docs build, and focused GUI/catalog regressions pass. All currently open inline review threads are resolved.

The PR remains draft and the C4 maintainer security-review requirement remains in place.

@lidge-jun

Copy link
Copy Markdown
Owner

Thank you for your patience — a review of this was prepared yesterday but not posted because the head moved mid-pass, so this is a full re-audit at 897d2660 against dev bef0dfbe. The good news first: the earlier privacy concern about raw-ID-derived selectors is not reproducible at this head. Unlabeled legacy accounts now get independent random pXXXXXX selectors, raw namespace targets are omitted from safeConfigDTO, and the focused request-log test confirms stored account IDs stay out of logs. Nice work on that.

Verdict: BLOCKED_SECURITY. The change routes credentials to models at an authentication boundary — an account-qualified selector overrides Direct/Pool selection and injects one stored credential at src/codex/auth-context.ts:130 — which MAINTAINERS.md gates behind explicit maintainer security approval. Four findings below, plus a conflict that must be resolved carefully.

1. High — the rebase must preserve current dev response handling. src/server/responses/core.ts:1213. This branch predates both the quota-aware subagent fallback series and #463's passthrough-error fix, which are now on dev. It has no applySubagentModelFallback or quota-terminal recording, and it no longer calls formatPassthroughUpstreamError before relaying non-2xx passthrough responses. Resolving the conflict in favor of the PR side would silently disable configured subagent fallback and bring back empty upstream 503s surfacing as opaque "Unknown error". Please rebase onto bef0dfbe, keep the current pipeline, and layer fixedAccount, account-qualified retry suppression, and safe namespace logging on top of it. An integration regression covering an account-qualified subagent request plus an empty non-2xx passthrough body would lock this down.

2. High — the collaboration conflict drops the current fallback guidance contract. src/server/responses/collaboration.ts:157. The PR's MultiAgentGuidanceOptions omits subagentModelFallback, removes subagentFallbackGuidanceText, and removes the {{fallback}} placeholder, all of which dev now exposes and persists. Taking the PR side leaves the management setting accepted but ignored when guidance is generated. Keep dev's option, text, and placeholder while integrating the account-aware roster projection, and test both contracts together including a bare native fallback projected onto account-qualified catalog entries.

3. Medium — account aliases can become persistent, privacy-sensitive log identifiers. src/codex/account-namespaces.ts:56. defaultPublicAccountLabel derives the namespace from any existing account alias, and alias validation at src/codex/auth-api.ts:571 permits email-like printable strings. That namespace then flows into catalog slugs and provider labels, while the full requested selector is persisted as requestedModel via src/server/responses/core.ts:685 and src/server/request-log.ts:580. So an alias like jun@example.com becomes jun-example.com and is retained in usage and request logs. Please don't derive telemetry/catalog selectors from the general display alias — default to the random pXXXXXX selector, or add a separate explicitly-public selector field with stricter validation and a PII warning. A regression proving display aliases and emails cannot reach catalog or persisted log fields would close this.

Note that bun run privacy:scan passing does not cover this: the scanner detects committed literal emails, home paths, and token-shaped strings, not runtime data flow.

4. Medium — picker-toggle refresh failures are reported as success. src/server/management/config-routes.ts:240. The settings route awaits refreshCodexCatalogBestEffort, which swallows refresh exceptions, then returns the enabled state, and gui/src/pages/CodexAuth.tsx:60 shows the success message. Your add and delete flows correctly return catalogRefreshPending — the primary enable/disable flow should too, so a failed refresh surfaces the existing recoverable ocx sync warning instead of leaving the persisted setting and the actual picker catalog silently out of sync.

Conflicts against dev bef0dfbegit merge-tree reports exactly two files: src/server/responses/collaboration.ts and src/server/responses/core.ts.

git fetch origin
git rebase bef0dfbed1863ccc1403112546e84abda5e9deb7
# resolve both files by preserving current dev behavior, then layering the namespace changes
git add src/server/responses/collaboration.ts src/server/responses/core.ts
GIT_EDITOR=true git rebase --continue
git push --force-with-lease

Please split this before the security review. At 61 files and +2706/-120, this is more than one security-sensitive review can responsibly cover. A stacked series, each targeting dev, independently green, ideally under ~500 changed lines:

  1. namespace foundation — src/types.ts, namespace helpers, config validation, collision rules, focused config/router unit tests
  2. exact-account routing — auth context, account health, router, HTTP/compact/WebSocket paths, fail-closed security regressions
  3. catalog/discovery — src/codex/catalog/*, /v1/models, account-aware subagent projection, catalog tests
  4. lifecycle and management UI — account add/delete, settings endpoints, refresh-failure reporting, GUI components and tests
  5. docs and localization — English reference/guides plus synchronized translations

Verification at 897d2660: typecheck passed; focused backend suite 556 passed / 0 failed across 14 files; full isolated suite 4,280 passed / 0 failed across 327 files; focused GUI suite 12 passed / 0 failed; GUI lint and production build passed; privacy scan passed. That is a genuinely solid state — the gap is that none of it validates the conflict resolution against current dev. Please rerun the full suite, typecheck, GUI lint/build, privacy scan, and focused integration tests after the rebase.


Full re-audit as part of a maintainer review pass, against dev at bef0dfbe, at head 897d2660. This supersedes the stale draft mentioned in the earlier marker.

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants