Skip to content

feat(codex): add account namespace foundation - #512

Closed
chrisae9 wants to merge 1 commit into
lidge-jun:devfrom
chrisae9:split/426-01-namespace-foundation
Closed

feat(codex): add account namespace foundation#512
chrisae9 wants to merge 1 commit into
lidge-jun:devfrom
chrisae9:split/426-01-namespace-foundation

Conversation

@chrisae9

@chrisae9 chrisae9 commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Part of #425. This is the namespace-foundation layer split out of #426 after the maintainer review.

What this PR does

  • adds an optional, explicitly schema-declared codexAccountNamespaces map from public model-selector namespaces to stored Codex account IDs;
  • defines one account-ID validator and small helpers for namespace matching, privacy-safe default generation, and the built-in account target;
  • rejects collisions with providers, combo aliases, reserved routing names, and account IDs at every existing creation boundary;
  • prevents default namespace assignment from producing an ambiguous namespace/account-ID map;
  • reserves __main__ for the internal Desktop account and uses @main only in the namespace map, leaving main available as a real pool-account ID;
  • omits the namespace-to-account mapping from the safe config DTO.

The provider-creation checks are deliberately narrow admission guards. The CLI, OAuth, management routes, and Alibaba startup migration do not create or rename namespaces here; they only refuse to overwrite one that is already configured. Management creation endpoints consistently return HTTP 409 for these namespace conflicts.

Account creation treats the live runtime configuration as authoritative. Manual import and OAuth creation reject a namespace collision before authentication work begins, then recheck after warmup immediately before credential persistence. OAuth reauthentication must still point to a configured pool row at that same boundary, so an account deleted while login is in progress is not recreated.

Generic OAuth provider login performs a namespace preflight before browser or CLI authentication, validates the provider row immediately before credential persistence, then reloads the latest configuration after the credential write and upserts only against that current provider row. This preserves same-provider API-key additions, removals, and switches that occur while credential persistence is pending. Kiro's credential transaction and rollback semantics remain intact. If a user-authored namespace claims the provider during credential persistence, the namespace wins: the credential remains saved, the provider remains absent from both disk and live routing, unrelated concurrent edits are retained, and CLI/GUI polling surfaces the recovery error before treating the credential as a successful login.

The management route no longer activates a provider when the authorization URL first appears: startLoginFlow completes background persistence first, then reconciles the settled disk state into the long-lived server object before status becomes done. Reconciliation is an in-place, recursive three-way merge from the normalized pre-login disk baseline, so unrelated disk edits are adopted while pending live mutations retain precedence and atomic Claude subtree conflicts remain safe. The hostname and port actually bound by the process remain runtime-only admission state, while concurrent disk hostname and port edits remain next-start intent for later saves. A successful login therefore activates the persisted provider row without restart and cannot erase concurrent live or disk configuration changes.

This layer is inert unless codexAccountNamespaces is explicitly configured. It does not add picker entries, route requests to an exact account, pin a conversation, alter Pool or Direct selection, or expose a UI setting. With no codexAccountNamespaces field, existing model-selection behavior is unchanged.

Privacy and collision rules

Generated public selectors never come from aliases, email addresses, stored account IDs, or deterministic ID fallbacks. Existing random log labels can be reused; legacy rows without one receive a fresh random selector. Explicit maps are preserved rather than silently renamed. Legacy account rows whose IDs cannot pass the namespace-target validator are omitted by the default and append builders rather than producing an invalid map.

Account IDs and selector targets reject JavaScript object-map keys such as __proto__, prototype, and constructor case-insensitively. Route matching and namespace-to-account ownership remain exact, while provider and reserved-namespace admission is case-insensitive so differently cased provider IDs cannot create ambiguous selector prefixes. The field-local schema validates raw own entries before Zod's record parser, and cross-field provider/combo collisions remain object-level validation. The strict account-ID validator protects account creation and OAuth login. Invalid legacy pool rows are excluded from account discovery, selection, quota priming, and credential resolution. Authenticated deletion keeps a narrow compatibility escape for an exact configured non-main row whose ID matched the legacy grammar, including __main__, so unsafe legacy credentials can be removed without touching the Desktop auth.json login or admitting new reserved IDs. A provider migration also stops without saving if its destination belongs to an account namespace.

The implementation footprint is 752 additions and 67 deletions under src/ and gui/src/, plus 50 documentation lines across the five configuration references. Tests add 1,664 lines and remove 8 lines across config-reload, namespace-allocation, persistence-race, provider/account admission, CLI, Kiro, and GUI regressions.

Follow-up layers

The remaining work stays separate as requested:

  1. exact-account request routing;
  2. catalog and discovery entries;
  3. account lifecycle integration and management UI.

This foundation intentionally does not introduce a partial config-locking abstraction. There is no runtime namespace writer in this layer, and the repository does not yet have a transaction primitive shared by every config writer; locking only these admission paths would imply a cross-process guarantee it cannot provide. The broader config transaction/lifecycle work remains in #425, alongside the already documented general config-write residual in #488.

Verification

  • bun run prepush
    • TypeScript typecheck
    • GUI lint
    • complete backend test suite, 0 failures
    • privacy scan
    • React Doctor completed; its advisory findings did not involve files changed by this PR
  • post-rebase focused config/OAuth/Kiro regressions: 53 tests, 0 failures
  • OAuth route/persistence stress: 20/20 reruns passed
  • documentation build: 136 pages
  • git diff --check

Checklist

  • Rebased onto current dev before the final push.
  • No exact-account routing, catalog entries, picker UI, or account-selection behavior is included.
  • Existing Pool, Direct, Responses, and subagent fallback behavior remains unchanged.
  • Generated selectors do not expose account identity or emit invalid legacy targets.
  • Account creation cannot claim an existing namespace, including configuration changes during warmup.
  • A late OAuth namespace claim leaves the provider absent from disk and live routing while preserving the credential, namespace, and unrelated edits.
  • OAuth live configuration is reconciled from settled persistence before clients observe a completed status.
  • Required upstream CI approved and green on the current head.
  • Fresh explicit maintainer security review required by MAINTAINERS.md.

Independent pre-review was used to catch ingress, migration, persistence, and live-runtime edge cases, but it does not replace the required maintainer approval.

Summary by CodeRabbit

  • New Features
    • Added optional codexAccountNamespaces configuration (supports @main), including namespace allocation and selector matching.
  • Bug Fixes
    • Prevented provider creation/updates, OAuth login, and CLI provider add/key logins when they collide with configured Codex account namespaces.
    • Blocked combo aliases that target configured Codex namespaces.
    • Improved Codex auth validation and ensured conflicts halt persistence; fixed login status/error precedence; improved live config reload behavior.
    • Alibaba region migration/startup now refuses reserved destination selectors.
  • Tests
    • Expanded coverage for namespace allocation, collision detection, reserved-ID validation, and “no config mutation on failure.”

@github-actions github-actions Bot added the enhancement New feature or request label Jul 26, 2026
@coderabbitai

This comment was marked as outdated.

@chrisae9
chrisae9 force-pushed the split/426-01-namespace-foundation branch from eab2c52 to 227e6df Compare July 26, 2026 18:47
@chrisae9
chrisae9 marked this pull request as ready for review July 26, 2026 18:49
chatgpt-codex-connector[bot]

This comment was marked as outdated.

coderabbitai[bot]

This comment was marked as resolved.

@chrisae9
chrisae9 force-pushed the split/426-01-namespace-foundation branch from 227e6df to c19ba6c Compare July 26, 2026 19:42
coderabbitai[bot]

This comment was marked as resolved.

Wibias

This comment was marked as outdated.

@Wibias
Wibias marked this pull request as draft July 26, 2026 20:30
@chrisae9
chrisae9 force-pushed the split/426-01-namespace-foundation branch from c19ba6c to 0718812 Compare July 26, 2026 20:49
@chrisae9
chrisae9 marked this pull request as ready for review July 26, 2026 20:52
chatgpt-codex-connector[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

Wibias

This comment was marked as outdated.

Wibias

This comment was marked as outdated.

@Wibias

This comment was marked as outdated.

@Wibias
Wibias marked this pull request as draft July 26, 2026 23:34
@chrisae9
chrisae9 force-pushed the split/426-01-namespace-foundation branch from 0718812 to b3aa8c6 Compare July 27, 2026 04:58
@chrisae9
chrisae9 marked this pull request as ready for review July 27, 2026 04:59
chatgpt-codex-connector[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

@chrisae9
chrisae9 force-pushed the split/426-01-namespace-foundation branch from b3aa8c6 to 9edaf62 Compare July 27, 2026 05:12
coderabbitai[bot]

This comment was marked as outdated.

@chrisae9
chrisae9 force-pushed the split/426-01-namespace-foundation branch from 9edaf62 to aef5628 Compare July 27, 2026 05:42
@chrisae9

This comment was marked as outdated.

@chrisae9
chrisae9 marked this pull request as ready for review July 27, 2026 17:59
@chrisae9

Copy link
Copy Markdown
Contributor Author

@Wibias Addressed both blockers from the full security re-review in signed commit afce6d40, rebased onto current dev (0 behind):

  1. Runtime listener identity and persisted next-start binding are now separate. OAuth keeps the actual socket hostname/port in the live object for admission, but later live saves serialize the operator’s adopted disk hostname/port, so a loopback next-start change is not reverted.
  2. Invalid legacy pool rows are quarantined from discovery, quota priming, selection, usability checks, and credential resolution. An exact configured non-main __main__ row makes Desktop selection fail closed until authenticated deletion removes only its managed credential/config row; auth.json remains untouched.

I also fixed CodeRabbit’s valid reauth metadata finding: a failed non-blocking WHAM usage probe no longer erases an existing pool plan.

Verification on this exact head:

  • bun run prepush passes, including full backend tests, privacy scan, and React Doctor;
  • 265 focused post-rebase tests pass;
  • OAuth reconciliation passed 20 reruns / 140 tests;
  • all 330 GUI tests across 75 files pass;
  • git diff --check passes.

The PR is ready again. Fresh fork-workflow approval and explicit maintainer authentication/security review are still required for afce6d40.

@chrisae9

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@chrisae9

Copy link
Copy Markdown
Contributor Author

@codex review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chrisae9

Copy link
Copy Markdown
Contributor Author

@Ingwannu or @lidge-jun, could you approve the fork workflows for exact head afce6d40 when convenient? Cross-platform CI and React Doctor are currently action_required; the local full pre-push, privacy, React Doctor, focused, stress, and GUI suites are green. This is also the head awaiting Wibias’s fresh authentication/security re-review.

@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: afce6d4072

ℹ️ 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/types.ts
Comment thread src/config.ts Outdated
Comment thread src/oauth/index.ts
Comment on lines +589 to +593
if (provider !== "chatgpt") {
const config = loadConfig();
const namespaceCollision = codexAccountNamespaceProviderCollisionError(config.codexAccountNamespaces, provider);
if (namespaceCollision) throw new Error(namespaceCollision);
}

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 Obtain explicit security review before merging

This change modifies OAuth authentication flow control, credential persistence ordering, and live activation of authenticated providers, so it falls squarely within the repository's security boundary. The commit description explicitly leaves fresh maintainer security review unchecked; do not merge these changes until an authorized maintainer has performed and recorded that review as required by MAINTAINERS.md.

AGENTS.md reference: AGENTS.md:L83-L89

Useful? React with 👍 / 👎.

@Wibias

Wibias commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Fix the remaining security blocker in PR #512 at head afce6d4.

reloadLiveConfigFromDisk() stores the persisted hostname and port in persistedLiveServerBinding, but saveConfigPreservingClaudeCode() later writes that cached snapshot without checking for newer operator edits on disk. A user can change the next-start binding to loopback after OAuth reconciliation, then an unrelated live save restores the older externally exposed binding.

Treat hostname and port as guarded persisted state with a baseline, not a permanent cached override:

  • keep the live config hostname/port tied to the socket Bun actually opened;
  • before every live-config save, read the current raw on-disk hostname/port;
  • if the operator changed either field since the last reconciliation, preserve the newest disk values in the persisted output;
  • update the persisted-binding baseline after a successful save;
  • never copy the live runtime binding into next-start configuration unless an actual validated rebind occurred.

Add a regression with this sequence:

  1. start with a live externally bound server;
  2. OAuth reconciliation adopts persisted binding A;
  3. edit disk hostname and port to loopback binding B;
  4. perform an unrelated saveConfigPreservingClaudeCode() call;
  5. assert the live config still reflects the open socket, while disk still contains binding B;
  6. reload and verify the next start would use binding B.

Keep the current Claude baseline rebase, OAuth completion ordering, late namespace handling, and legacy __main__ quarantine/cleanup unchanged.

Then rerun full cross-platform CI and obtain fresh maintainer security approval on the final head.

@Wibias
Wibias marked this pull request as draft July 27, 2026 18:12
coderabbitai[bot]

This comment was marked as resolved.

@chrisae9

Copy link
Copy Markdown
Contributor Author

Addressed Wibias's binding-baseline blocker in 8e584f5. Live hostname/port remain tied to the open socket; each guarded live save rereads the raw persisted binding, preserves a newer valid disk edit, and advances the persisted baseline only after save succeeds. The regression now covers external runtime bind -> OAuth adopts A -> disk edited to loopback B -> unrelated live save, then verifies runtime still reflects the open socket and a fresh load retains B. The branch is also rebased onto dev 461de39. Full test suite, typecheck, GUI lint, focused GUI test, and the upstream OAuth API-key preservation regressions pass.

@Wibias

Wibias commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

@lidge-jun

@Wibias
Wibias marked this pull request as ready for review July 27, 2026 20:20

@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: 8e584f52dc

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

Comment thread src/oauth/index.ts Outdated
@Wibias

Wibias commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Resolve conflicts pls @chrisae9 then @lidge-jun or @Ingwannu please code owner sec research + merge.

@chrisae9
chrisae9 force-pushed the split/426-01-namespace-foundation branch from 8e584f5 to 1c24609 Compare July 27, 2026 23:46
@chrisae9

Copy link
Copy Markdown
Contributor Author

Author update on 1c24609:

  • rebased onto current dev at 65e3fee and resolved the OAuth/Kiro overlap while preserving upstream Kiro transaction rollback behavior;
  • fixed the same-provider persistence race by reloading the latest row after credential save and re-running the OAuth upsert against it, so concurrent API-key additions, removals, and active-key switches survive;
  • replaced whole-config OAuth settlement with an in-place recursive three-way reconciliation that preserves pending live mutations while adopting unrelated persisted edits;
  • added the configuration-field documentation to all five maintained references;
  • replied to and resolved the three addressed review threads; the two explicit maintainer security-review gates remain open;
  • bun run prepush is green: typecheck, GUI lint, 5,503 tests across 397 files, privacy scan, and React Doctor completed;
  • focused post-rebase OAuth, Kiro, and config regressions pass, OAuth race stress passed 20/20 runs, the docs build produced 136 pages, and git diff --check is clean;
  • the branch remains one signed commit and is 0 behind dev.

The exact-head fork workflows may require maintainer approval before they run. @lidge-jun or @Ingwannu, this head is ready for the requested code-owner authentication/security review when convenient.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== PR metadata ==='
gh pr view 512 --repo lidge-jun/opencodex --json number,headRefOid,baseRefOid,files --jq '{number, headRefOid, baseRefOid, files: [.files[].path]}'

echo '=== Changed files (stat) ==='
git diff --stat origin/dev...HEAD

echo '=== Changed production/test files ==='
git diff --name-only origin/dev...HEAD

echo '=== Structural outlines of primary changed modules ==='
for f in src/config.ts src/oauth/index.ts src/server/management/oauth-account-routes.ts; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    ast-grep outline "$f" --items all --view expanded || true
  fi
done

echo '=== Relevant definitions and call sites ==='
rg -n -C 4 --glob '*.ts' --glob '*.tsx' \
  'function (reloadLiveConfigFromDisk|saveConfigPreservingClaudeCode|saveRuntimeConfig|runLogin|upsertOAuthProvider)|\b(reloadLiveConfigFromDisk|saveConfigPreservingClaudeCode|saveRuntimeConfig|runLogin|upsertOAuthProvider)\s*\(' \
  src tests gui || true

echo '=== Exact diff for configuration/OAuth paths ==='
git diff --find-renames --unified=80 origin/dev...HEAD -- \
  src/config.ts src/oauth/index.ts src/server/management/oauth-account-routes.ts \
  tests/config.test.ts tests/oauth-public-surface.test.ts

Length of output: 3351


🏁 Script executed:

#!/bin/bash
set -euo pipefail

base='65e3fee1f8916ad08f61a0297da91aab05714795'

echo '=== Repository refs / base availability ==='
git status --short
git show -s --format='%H %P %s' HEAD
git cat-file -t "$base"

echo '=== PR diff summary ==='
git diff --stat "$base"...HEAD
git diff --name-only "$base"...HEAD

echo '=== Outlines: primary production and regression files ==='
for f in \
  src/config.ts \
  src/oauth/index.ts \
  src/server/management/oauth-account-routes.ts \
  tests/config-user-edits.test.ts \
  tests/oauth-public-surface.test.ts \
  tests/kiro-review-regressions.test.ts
do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    ast-grep outline "$f" --items all --view expanded || true
  fi
done

echo '=== Definitions/call sites with context ==='
rg -n -C 5 --glob '*.ts' --glob '*.tsx' \
  'function (reloadLiveConfigFromDisk|saveConfigPreservingClaudeCode|saveRuntimeConfig|reconcile|runLogin|upsertOAuthProvider)|\b(reloadLiveConfigFromDisk|saveConfigPreservingClaudeCode|saveRuntimeConfig|runLogin|upsertOAuthProvider)\s*\(' \
  src tests gui || true

echo '=== Exact relevant diff ==='
git diff --find-renames --unified=100 "$base"...HEAD -- \
  src/config.ts src/oauth/index.ts src/server/management/oauth-account-routes.ts \
  tests/config-user-edits.test.ts tests/config.test.ts tests/oauth-public-surface.test.ts \
  tests/kiro-review-regressions.test.ts

Length of output: 107094


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== Configuration symbols and exact ranges ==='
rg -n -C 12 \
  'persistedLiveServerBinding|function (readPersisted|reconcileLiveConfigFromDisk|reloadLiveConfigFromDisk|saveConfigPreservingClaudeCode|armClaudeCodeBaseline)|export function (reconcileLiveConfigFromDisk|reloadLiveConfigFromDisk|saveConfigPreservingClaudeCode|armClaudeCodeBaseline)' \
  src/config.ts

echo '=== Read relevant config source slices ==='
for range in 1:260 720:1060 1060:1320; do
  start=${range%:*}; end=${range#*:}
  echo "--- src/config.ts:$start-$end ---"
  sed -n "${start},${end}p" src/config.ts
done

echo '=== All callers and tests of reconciliation/binding-safe saving ==='
rg -n -C 6 --glob '*.ts' --glob '*.tsx' \
  '\b(reconcileLiveConfigFromDisk|reloadLiveConfigFromDisk|saveConfigPreservingClaudeCode|armClaudeCodeBaseline)\s*\(' \
  src tests gui

echo '=== OAuth-related diff only, compact ==='
base='65e3fee1f8916ad08f61a0297da91aab05714795'
git diff --unified=30 "$base"...HEAD -- src/config.ts src/oauth/index.ts src/server/management/oauth-account-routes.ts

echo '=== Locate tests exercising deletion and nested/array conflicts ==='
rg -n -C 5 --glob '*.test.ts' --glob '*.test.tsx' \
  'reconcileLiveConfigFromDisk|three-way|pending live|nested|delet|array|hostname|persisted.*binding' \
  tests gui/tests

Length of output: 191330


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== Exact symbol locations ==='
rg -n \
  'persistedLiveServerBinding|function (reconcileLiveConfigFromDisk|reloadLiveConfigFromDisk|saveConfigPreservingClaudeCode|armClaudeCodeBaseline|merge)|export function (reconcileLiveConfigFromDisk|reloadLiveConfigFromDisk|saveConfigPreservingClaudeCode|armClaudeCodeBaseline)' \
  src/config.ts

echo '=== Relevant config implementation (line-numbered) ==='
nl -ba src/config.ts | sed -n '600,920p'
nl -ba src/config.ts | sed -n '920,1160p'

echo '=== Relevant config diff only ==='
base='65e3fee1f8916ad08f61a0297da91aab05714795'
git diff --unified=50 "$base"...HEAD -- src/config.ts

echo '=== Direct save/reconcile callers (production only) ==='
rg -n -C 3 --glob '*.ts' \
  '\b(reconcileLiveConfigFromDisk|reloadLiveConfigFromDisk|saveConfigPreservingClaudeCode)\s*\(' \
  src

Length of output: 1884


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== src/config.ts Lines 1110-1350 ==='
awk 'NR >= 1110 && NR <= 1350 { printf "%6d  %s\n", NR, $0 }' src/config.ts

echo '=== src/config.ts changed hunks only ==='
base='65e3fee1f8916ad08f61a0297da91aab05714795'
git diff --unified=35 "$base"...HEAD -- src/config.ts

echo '=== Direct production callers ==='
rg -n -C 3 --glob '*.ts' \
  '\b(reconcileLiveConfigFromDisk|reloadLiveConfigFromDisk|saveConfigPreservingClaudeCode)\s*\(' \
  src

Length of output: 58404


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== Exact head commit ==='
git show -s --format='commit=%H%nparent=%P%nsubject=%s%n%n%b' HEAD
echo
echo '=== Files changed by HEAD only ==='
git diff-tree --no-commit-id --name-status -r HEAD
echo
echo '=== HEAD-only diff stat ==='
git show --stat --oneline HEAD

echo '=== HEAD-only source/test diff ==='
git show --find-renames --unified=80 -- \
  src/config.ts src/oauth/index.ts src/server/management/oauth-account-routes.ts \
  tests/config-user-edits.test.ts tests/oauth-public-surface.test.ts \
  tests/kiro-review-regressions.test.ts

echo '=== OAuth config-save sequencing and direct save implementation ==='
rg -n -C 8 --glob '*.ts' \
  'async function runLogin|export async function runLogin|saveLatestConfig\(|loadLatestConfig\(|saveConfigPreservingClaudeCode\(' \
  src/oauth/index.ts src/config.ts src/server/management/oauth-account-routes.ts

echo '=== Existing focused regression names ==='
rg -n --glob '*.test.ts' --glob '*.test.tsx' \
  'same-provider|pending live|late namespace|reconcile|binding|Kiro CLI recovery' \
  tests/oauth-public-surface.test.ts tests/config-user-edits.test.ts tests/kiro-review-regressions.test.ts

Length of output: 134055


@chrisae9 Review complete for 1c246090.

I found no blocking issues in the updated OAuth/config persistence paths.

In particular, the final implementation correctly addresses the prior failure modes:

  • src/oauth/index.ts Lines 681–750 reloads the latest configuration after credential persistence, then re-runs the provider upsert. This preserves same-provider API-key pool changes made during the credential write and prevents a late namespace claim from creating a provider row.
  • src/server/management/oauth-account-routes.ts Lines 91–108 uses the persisted configuration—not the mutable live object—as the three-way merge base, then reconciles before OAuth status becomes settled.
  • src/config.ts Lines 1172–1228 retains pending live mutations on same-leaf conflicts while merging disjoint persisted changes.
  • src/config.ts Lines 1236–1263 and Lines 1315–1344 keep runtime hostname/port tied to the already-open socket, while rereading and preserving newer on-disk next-start binding edits before every guarded save.
  • The focused regressions cover the key late-collision, same-provider key, pending-live-edit, Claude baseline, and binding-preservation scenarios.

This was a static review; I did not execute the test suite in the sandbox.

@chrisae9
chrisae9 force-pushed the split/426-01-namespace-foundation branch from 1c24609 to ee7b7db Compare July 27, 2026 23:56
@Wibias Wibias linked an issue Jul 28, 2026 that may be closed by this pull request
3 tasks
@Wibias

Wibias commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Will get merged after interal code work lands on dev.

@lidge-jun

Copy link
Copy Markdown
Owner

Merged into dev as c9bed7c with your authorship preserved.

This landed as a rebase rather than a merge commit, so GitHub cannot detect it automatically and the PR has to be closed by hand — the code is in dev, not dropped.

Security review (required by MAINTAINERS.md) — recorded here as the explicit maintainer review this PR was waiting on.

  • runLogin persistence ordering: the provider row is validated against a loaded snapshot before saveCredential, and the post-credential path re-checks the namespace map before upserting. A late namespace claim now fails with the credential saved and the provider absent from both disk and live routing, which is the fail-closed direction. The regression in tests/oauth-public-surface.test.ts covers the post-save window.
  • Reserved-key handling: __proto__/prototype/constructor are rejected case-insensitively for both account ids and selector keys, and the field-local schema inspects raw own entries before Zod's record parser drops __proto__. @main sits outside the pool-account grammar, so main stays usable as a real account id.
  • Privacy: codexAccountNamespaces and its account-id targets stay out of safeConfigDTO, asserted in tests/server-auth.test.ts. Generated selectors do not derive from aliases, emails, or stored ids.
  • Legacy rows: invalid ids are excluded from selection, quota priming, and credential resolution, while deletion keeps a narrow exact-match escape so unsafe legacy credentials remain removable.

Rebase conflict resolved during merge. dev moved 26 commits ahead of this branch while it waited, and src/codex/routing.ts conflicted in getEligiblePoolAccounts: #599 replaced the cooldown filter with getCodexQuotaHealthSnapshot(account.id, quotaScope, now) on the same lines where this PR swapped !account.isMain for isSelectableCodexPoolAccount(account). Both were kept — the new quota-scoped health check plus your account-id validation. Please sanity-check that resolution.

Verified on the final rebased tip: typecheck, GUI lint, privacy scan, and the full suite at 5865 pass / 0 fail.

Thanks for splitting this out of #426 and for the detailed persistence-ordering work — the follow-up layers (routing, catalog, lifecycle UI) remain tracked in #425.

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.

Expose Codex accounts as model namespaces in the picker

4 participants