Skip to content

feat(codex): force a specific account per invocation with --account (#623) - #624

Merged
ndycode merged 3 commits into
mainfrom
feat/623-force-account
Jul 9, 2026
Merged

feat(codex): force a specific account per invocation with --account (#623)#624
ndycode merged 3 commits into
mainfrom
feat/623-force-account

Conversation

@ndycode

@ndycode ndycode commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Closes #623.

Summary

  • Adds codex-multi-auth-codex --account <index|email|id> (and the equivalent CODEX_MULTI_AUTH_FORCE_ACCOUNT env var) to pin a single account for one forwarded Codex run — for users who keep separate account pools (e.g. personal vs. work) and want a specific invocation to use a specific account (e.g. when driving Codex from another tool).

What Changed

  • Launcher (scripts/codex.js): parses --account / --account= (flag wins over CODEX_MULTI_AUTH_FORCE_ACCOUNT), strips the launcher-only flag from the args forwarded to real Codex, resolves the selector to a 0-based index against the same scoped accounts pool the proxy loads, and publishes CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX for the proxy. Selector forms: 1-based index, email, or account id.
  • Runtime rotation proxy (lib/runtime-rotation-proxy.ts, rotation-proxy-state.ts, rotation-server-types.ts): new forcedAccountIndex option (falls back to the env var so it survives the launcher → detached app-helper process boundary), consumed as pinnedIndex = state.forcedAccountIndex ?? storageMeta.pinnedAccountIndex. This reuses the existing deterministic-pin path, so no other selection logic changes.
  • Ephemeral & leak-safe: the pin lives only on that invocation's own proxy instance, never mutates the persisted switch pin, and cannot leak across concurrent sessions.
  • Fail-hard by design: an unavailable target yields the existing codex_pinned_account_unavailable error rather than spilling onto another account; the wrapper exits non-zero (without launching Codex) when the selector does not resolve or the runtime rotation proxy is disabled.
  • Extension point for pools: selector handling is isolated so a future --account <group> can layer on without reworking the single-pin path (documented, not built).

Validation

  • npm run lint
  • npm run typecheck
  • npm test — ran the affected suites in full: test/runtime-rotation-proxy.test.ts and test/codex-bin-wrapper.test.ts (195 passing, incl. 8 new). One pre-existing, unrelated proxy test (evicts oldest local thread goal fallbacks…) hits the default 5s vitest timeout on this Windows box — reproduced identically on clean main; passes at --testTimeout=20000.
  • npm test -- test/documentation.test.ts
  • npm run build

Docs and Governance Checklist

  • README updated (Daily use command table)
  • docs/getting-started.md (onboarding flow unchanged)
  • docs/features.md updated (new capability row)
  • relevant docs/reference/* pages updated (commands.md flag + dedicated section; configuration.md env vars + proxy note)
  • docs/upgrade.md (no migration behavior change)
  • SECURITY.md / CONTRIBUTING.md reviewed — no account emails/tokens added to proxy client response headers or logs; selector-error output lists emails only on the user's own stderr, consistent with existing list/status output.

Risk and Rollback

  • Risk level: Low. Additive; when --account/env are unset, behavior is byte-for-byte unchanged (the internal index env is also proactively cleared). Reuses the audited deterministic-pin path.
  • Rollback plan: revert this PR; no storage/migration changes, so no data cleanup needed.

Additional Notes

  • New tests: proxy-level (deterministic selection to the forced account, fail-hard 503 with no upstream call, normalizeForcedAccountIndex unit) and launcher-level (missing value, rotation disabled, out-of-range, strip + index publish).
  • CHANGELOG: added under ## [Unreleased].

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a way to force a single account for one Codex wrapper run using --account <index|email|id> or an environment variable.
    • The selected account is used only for that invocation and does not change the saved default account.
  • Bug Fixes

    • Improved failure handling when a forced account can’t be used or the rotation proxy is disabled, preventing fallback to another account.
  • Documentation

    • Updated usage, configuration, and command reference docs to cover the new account-forcing behavior.

note: greptile review for oc-chatgpt-multi-auth. cite files like lib/foo.ts:123. confirm regression tests + windows concurrency/token redaction coverage.

Greptile Summary

adds codex-multi-auth-codex --account <index|email|id> (and CODEX_MULTI_AUTH_FORCE_ACCOUNT) to pin a single account for one forwarded codex run without touching the persisted switch pin. the implementation is additive and fail-hard: an unknown selector or disabled proxy exits non-zero before launching codex, and the forced pin reuses the existing deterministic-pin path in the proxy with no rotation.

  • launcher (scripts/codex.js): parses and strips --account/--account=, resolves the selector to a 0-based index against the scoped accounts pool, and publishes CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX; also removes the index from the background forecast child's env to prevent pin bleed.
  • proxy (lib/runtime-rotation-proxy.ts, rotation-proxy-state.ts, rotation-server-types.ts): normalizeForcedAccountIndex coerces the option or env string; state.forcedAccountIndex ?? storageMeta.pinnedAccountIndex plumbs the ephemeral pin into the existing deterministic-pin path per request.
  • tests (test/codex-bin-wrapper.test.ts, test/runtime-rotation-proxy.test.ts): 8 new tests covering error paths, all selector forms (numeric/email/id/env/equals), flag-wins-over-env precedence, fail-hard 503 with no upstream call, and the detached app-helper env boundary.

Confidence Score: 5/5

safe to merge; the feature is purely additive and the existing forwarding path is byte-for-byte unchanged when --account and CODEX_MULTI_AUTH_FORCE_ACCOUNT are both unset.

the new code is well-scoped: flag parsing, selector resolution, and env publication are isolated to the launcher; the proxy change is a single ?? expression that reuses the audited deterministic-pin path. the background refresh child env cleanup correctly deletes the internal index var, though the user-facing selector is left (flagged as a suggestion). no storage changes, no concurrency issues on the proxy side since forcedAccountIndex lives in per-instance state.

scripts/codex.js — the background refresh child inherits CODEX_MULTI_AUTH_FORCE_ACCOUNT (user-facing selector) even though CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX (resolved index) is correctly deleted.

Important Files Changed

Filename Overview
scripts/codex.js adds --account flag parsing, selector resolution, and env publication; correct overall but CODEX_MULTI_AUTH_FORCE_ACCOUNT is not scrubbed from the background refresh child env
lib/runtime-rotation-proxy.ts adds normalizeForcedAccountIndex and wires forcedAccountIndex into the per-request pinnedIndex path; ?? correctly handles index 0; logic is sound
lib/runtime/rotation-proxy-state.ts adds required forcedAccountIndex field to RotationProxyStateInit; clean type change
lib/runtime/rotation-server-types.ts adds optional forcedAccountIndex to RuntimeRotationProxyOptions with clear fallback-to-env semantics documented
test/runtime-rotation-proxy.test.ts adds normalizeForcedAccountIndex unit tests and proxy-level pin/fail-hard/env-crossing tests; afterEach cleanup for CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX is correct
test/codex-bin-wrapper.test.ts adds 8 new launcher tests covering error paths, numeric/email/id/env/equals-form selectors, flag-wins-over-env, and the detached app-helper boundary; solid coverage
test/rotation-proxy-state.test.ts adds forcedAccountIndex: null to stateInit fixture; minimal required update

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    actor User
    participant Launcher as scripts/codex.js
    participant Proxy as runtime-rotation-proxy.ts
    participant Account as AccountManager
    participant Codex as Real Codex CLI

    User->>Launcher: codex-multi-auth-codex --account 2 exec
    Launcher->>Launcher: "extractForcedAccountFlag()<br/>strips --account 2, selector=2"
    Launcher->>Launcher: isRuntimeRotationProxyEnabled() → true
    Launcher->>Launcher: "resolveForcedAccountIndex(2)<br/>reads accounts.json, returns index=1"
    Launcher->>Launcher: "set CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX=1"
    Launcher->>Proxy: "startRuntimeRotationProxy({forcedAccountIndex: 1})"
    Proxy->>Proxy: "normalizeForcedAccountIndex(option ?? env)<br/>stores in state.forcedAccountIndex=1"
    Launcher->>Codex: forward exec (stripped args, no --account)
    Codex->>Proxy: POST /v1/responses
    Proxy->>Proxy: "pinnedIndex = state.forcedAccountIndex(1) ?? storageMeta.pinnedAccountIndex"
    Proxy->>Account: pick account[1] (deterministic, no rotation)
    Account-->>Proxy: access token
    Proxy->>Proxy: inject auth headers for account[1]
    Proxy-->>Codex: 200 OK (streamed)
    Codex-->>User: output
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    actor User
    participant Launcher as scripts/codex.js
    participant Proxy as runtime-rotation-proxy.ts
    participant Account as AccountManager
    participant Codex as Real Codex CLI

    User->>Launcher: codex-multi-auth-codex --account 2 exec
    Launcher->>Launcher: "extractForcedAccountFlag()<br/>strips --account 2, selector=2"
    Launcher->>Launcher: isRuntimeRotationProxyEnabled() → true
    Launcher->>Launcher: "resolveForcedAccountIndex(2)<br/>reads accounts.json, returns index=1"
    Launcher->>Launcher: "set CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX=1"
    Launcher->>Proxy: "startRuntimeRotationProxy({forcedAccountIndex: 1})"
    Proxy->>Proxy: "normalizeForcedAccountIndex(option ?? env)<br/>stores in state.forcedAccountIndex=1"
    Launcher->>Codex: forward exec (stripped args, no --account)
    Codex->>Proxy: POST /v1/responses
    Proxy->>Proxy: "pinnedIndex = state.forcedAccountIndex(1) ?? storageMeta.pinnedAccountIndex"
    Proxy->>Account: pick account[1] (deterministic, no rotation)
    Account-->>Proxy: access token
    Proxy->>Proxy: inject auth headers for account[1]
    Proxy-->>Codex: 200 OK (streamed)
    Codex-->>User: output
Loading

Comments Outside Diff (1)

  1. test/codex-bin-wrapper.test.ts, line 469-551 (link)

    P2 missing vitest coverage for the env-var fallback path and non-numeric selectors

    the four new launcher tests only exercise the numeric --account flag form. three paths that could silently break are not covered:

    • CODEX_MULTI_AUTH_FORCE_ACCOUNT=<selector> env var fallback (the flag-vs-env precedence logic in resolveForcedAccountSelector is only reachable via this path)
    • email selector (--account work@example.com) and account-id selector (--account acc_...) against findAccountIndexByIdOrEmail
    • --account=<value> equals-sign form (parsed separately from the space form in extractForcedAccountFlag)

    writeAccountsFixture already provides everything needed to write these; adding one test per path would close the gap.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: test/codex-bin-wrapper.test.ts
    Line: 469-551
    
    Comment:
    **missing vitest coverage for the env-var fallback path and non-numeric selectors**
    
    the four new launcher tests only exercise the numeric `--account` flag form. three paths that could silently break are not covered:
    
    - `CODEX_MULTI_AUTH_FORCE_ACCOUNT=<selector>` env var fallback (the flag-vs-env precedence logic in `resolveForcedAccountSelector` is only reachable via this path)
    - email selector (`--account work@example.com`) and account-id selector (`--account acc_...`) against `findAccountIndexByIdOrEmail`
    - `--account=<value>` equals-sign form (parsed separately from the space form in `extractForcedAccountFlag`)
    
    `writeAccountsFixture` already provides everything needed to write these; adding one test per path would close the gap.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

    Fix in Codex

Reviews (2): Last reviewed commit: "test/fix: address code review on --accou..." | Re-trigger Greptile

Neil and others added 2 commits July 9, 2026 19:01
…623)

Add `codex-multi-auth-codex --account <index|email|id>` (and the equivalent
CODEX_MULTI_AUTH_FORCE_ACCOUNT env var) to pin a single account for one
forwarded Codex run, for users who keep separate account pools and want a
specific invocation to use a specific account.

Mechanism: the launcher resolves the selector to a 0-based index against the
same scoped accounts pool the proxy loads, strips the launcher-only flag from
the args forwarded to real Codex, and publishes CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX.
The runtime rotation proxy consumes it as an ephemeral pin
(`pinnedIndex = state.forcedAccountIndex ?? storageMeta.pinnedAccountIndex`),
reusing the existing deterministic-pin path — so it never mutates the persisted
`switch` pin and cannot leak across concurrent sessions (each invocation owns
its own proxy instance).

Fail-hard by design: an unavailable target yields the existing
codex_pinned_account_unavailable error instead of spilling onto another
account, and the wrapper exits non-zero (without launching Codex) when the
selector does not resolve or the runtime rotation proxy is disabled.

Tests: proxy-level (deterministic selection, fail-hard, normalizeForcedAccountIndex)
and launcher-level (missing value, rotation disabled, out-of-range, strip +
index publish). Docs updated (commands reference, configuration, CHANGELOG).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…623)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR adds an ephemeral, per-invocation account-forcing capability to codex-multi-auth-codex via a new --account <index|email|id> flag (and CODEX_MULTI_AUTH_FORCE_ACCOUNT env var). The launcher resolves the selector, requires the runtime rotation proxy to be enabled, and publishes CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX for that run's proxy instance, which prioritizes it over the persisted pin without mutating disk state. Includes docs and tests.

Changes

Force Account Per Invocation

Layer / File(s) Summary
Rotation proxy forced-account contract and selection
lib/runtime/rotation-proxy-state.ts, lib/runtime/rotation-server-types.ts, lib/runtime-rotation-proxy.ts
Adds forcedAccountIndex to state/options interfaces, exports normalizeForcedAccountIndex, resolves the pin at proxy startup, and prioritizes it over persisted storageMeta.pinnedAccountIndex during request selection.
Launcher --account flag and forwarding
scripts/codex.js
Parses/strips --account/CODEX_MULTI_AUTH_FORCE_ACCOUNT, resolves the selector against the accounts pool, requires the runtime rotation proxy, publishes CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX, and wires forced selection into main() before status-line printing and forwarding.
Tests for forced account behavior
test/codex-bin-wrapper.test.ts, test/runtime-rotation-proxy.test.ts
Adds wrapper tests for missing/invalid --account, disabled-proxy rejection, out-of-range selection, and successful pinning; adds proxy tests for normalizeForcedAccountIndex parsing and forced routing/failure behavior.
Documentation updates
CHANGELOG.md, README.md, docs/configuration.md, docs/features.md, docs/reference/commands.md
Documents --account, CODEX_MULTI_AUTH_FORCE_ACCOUNT, and CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX behavior, ephemeral/fail-hard semantics, and runtime rotation proxy requirement.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CodexJS as scripts/codex.js
  participant AccountsPool
  participant RuntimeRotationProxy

  User->>CodexJS: codex-multi-auth-codex --account <selector>
  CodexJS->>CodexJS: strip --account, extract selector
  CodexJS->>AccountsPool: load openai-codex-accounts.json
  CodexJS->>CodexJS: resolve selector to 0-based index
  alt runtime rotation proxy disabled
    CodexJS-->>User: hard error, exit non-zero
  else proxy enabled
    CodexJS->>RuntimeRotationProxy: set CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX
    RuntimeRotationProxy->>RuntimeRotationProxy: pinnedIndex = forcedAccountIndex ?? storageMeta.pinnedAccountIndex
    CodexJS->>CodexJS: forward stripped args to real Codex
  end
Loading

Possibly related PRs

  • ndycode/codex-multi-auth#480: Both modify lib/runtime-rotation-proxy.ts's pinned/account selection and routing logic.
  • ndycode/codex-multi-auth#500: Both modify scripts/codex.js's forwarding flow around status-line and quota-cache refresh, which now runs after forced-account resolution.

Suggested labels: passed

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ⚠️ Warning the title is on-topic, but it exceeds the 72-char limit and is not strictly within the required conventional-commit format. shorten the summary to 72 characters or less and keep the conventional-commit format, e.g. feat(codex): force one account per invocation.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR implements the requested --account support to force a specific account for a single codex-multi-auth-codex invocation.
Out of Scope Changes check ✅ Passed The docs, tests, and proxy/runtime updates are all directly tied to the account-forcing feature and appear in scope.
Description check ✅ Passed the description matches the template and covers summary, changes, validation, docs, risk, and notes; the unchecked items are acceptable.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/623-force-account
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/623-force-account

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.

Comment on lines +621 to +623
const parsed =
typeof value === "number" ? value : Number.parseInt(value.trim(), 10);
if (!Number.isInteger(parsed) || parsed < 0) {

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 normalizeForcedAccountIndex uses Number.parseInt for string input, which silently truncates floats: normalizeForcedAccountIndex("1.5") returns 1 instead of null. the number-type path correctly rejects 1.5 via Number.isInteger, but the string path doesn't. CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX is always published as String(integer) by the launcher, so this only bites someone who sets the internal env var by hand — but the asymmetry is a test gap and could mask a stale/corrupt env value being silently accepted.

Suggested change
const parsed =
typeof value === "number" ? value : Number.parseInt(value.trim(), 10);
if (!Number.isInteger(parsed) || parsed < 0) {
const trimmed = typeof value === "number" ? null : value.trim();
const parsed =
trimmed !== null
? Number.parseInt(trimmed, 10)
: value;
if (
trimmed !== null &&
Number.isInteger(parsed) &&
String(parsed) !== trimmed
) {
return null;
}
if (!Number.isInteger(parsed) || parsed < 0) {
Prompt To Fix With AI
This is a comment left during a code review.
Path: lib/runtime-rotation-proxy.ts
Line: 621-623

Comment:
`normalizeForcedAccountIndex` uses `Number.parseInt` for string input, which silently truncates floats: `normalizeForcedAccountIndex("1.5")` returns `1` instead of `null`. the number-type path correctly rejects `1.5` via `Number.isInteger`, but the string path doesn't. `CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX` is always published as `String(integer)` by the launcher, so this only bites someone who sets the internal env var by hand — but the asymmetry is a test gap and could mask a stale/corrupt env value being silently accepted.

```suggestion
	const trimmed = typeof value === "number" ? null : value.trim();
	const parsed =
		trimmed !== null
			? Number.parseInt(trimmed, 10)
			: value;
	if (
		trimmed !== null &&
		Number.isInteger(parsed) &&
		String(parsed) !== trimmed
	) {
		return null;
	}
	if (!Number.isInteger(parsed) || parsed < 0) {
```

How can I resolve this? If you propose a fix, please make it concise.

Fix in Codex

Comment thread scripts/codex.js
Comment on lines +332 to +344
if (arg === "--account") {
sawFlag = true;
const next = args[index + 1];
if (typeof next !== "string") {
return {
selector: null,
strippedArgs: args,
error:
"codex-multi-auth: --account requires a value (account index, email, or account id).",
};
}
selector = next;
index += 1;

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 --account followed by a flag-looking value is silently consumed

typeof next !== "string" only guards against end-of-args (undefined). if a user mistypes codex-multi-auth-codex --account --model gpt-5-codex, the string "--model" is swallowed as the account selector and "gpt-5-codex" lands in strippedArgs as a bare positional — the forwarded codex invocation then sees gpt-5-codex as a positional argument rather than a flag value. the resulting error ("did not match any configured account") is confusing because the real mistake is a missing account value. adding a check like if (next.startsWith("--")) before consuming it would surface the right error message.

Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/codex.js
Line: 332-344

Comment:
**`--account` followed by a flag-looking value is silently consumed**

`typeof next !== "string"` only guards against end-of-args (`undefined`). if a user mistypes `codex-multi-auth-codex --account --model gpt-5-codex`, the string `"--model"` is swallowed as the account selector and `"gpt-5-codex"` lands in `strippedArgs` as a bare positional — the forwarded codex invocation then sees `gpt-5-codex` as a positional argument rather than a flag value. the resulting error ("did not match any configured account") is confusing because the real mistake is a missing account value. adding a check like `if (next.startsWith("--"))` before consuming it would surface the right error message.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Codex

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 `@docs/features.md`:
- Line 39: Update the “Per-invocation account pin” row in the documentation
table so it describes a single forwarded Codex invocation rather than a broader
wrapper session. Keep the wording aligned with the launcher/runtime contract
used by codex-multi-auth-codex and its --account flag, and make clear that the
pin is ephemeral for one run only and does not persist beyond that invocation.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 527bf60f-427a-4045-8ef6-403c5fcdb035

📥 Commits

Reviewing files that changed from the base of the PR and between 8360f85 and dc01587.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • README.md
  • docs/configuration.md
  • docs/features.md
  • docs/reference/commands.md
  • lib/runtime-rotation-proxy.ts
  • lib/runtime/rotation-proxy-state.ts
  • lib/runtime/rotation-server-types.ts
  • scripts/codex.js
  • test/codex-bin-wrapper.test.ts
  • test/runtime-rotation-proxy.test.ts

Comment thread docs/features.md
| --- | --- | --- |
| Local Responses proxy | Routes forwarded official Codex Responses/model traffic through a loopback provider named `codex-multi-auth-runtime-proxy` | `codex-multi-auth rotation status` |
| Per-request account rotation | Moves to another managed account on quota, auth refresh, network, or server failure before streaming response bytes | runtime proxy |
| Per-invocation account pin | Forces one account for a single wrapper session (ephemeral, fail-hard, never touches the persisted `switch` pin) | `codex-multi-auth-codex --account <index\|email\|id>` |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Tighten the scope wording.

This reads like a broader wrapper session, but the feature is scoped to one forwarded Codex invocation. Align the table text with the launcher/runtime contract so users don’t expect the pin to survive beyond a single run.

As per coding guidelines: keep terminology consistent with runtime names in documentation. Based on learnings: the PR objective and launcher contract describe a single forwarded invocation.

🔧 Suggested wording
-| Per-invocation account pin | Forces one account for a single wrapper session (ephemeral, fail-hard, never touches the persisted `switch` pin) | `codex-multi-auth-codex --account <index\|email\|id>` |
+| Per-invocation account pin | Forces one account for a single forwarded Codex invocation (ephemeral, fail-hard, never touches the persisted `switch` pin) | `codex-multi-auth-codex --account <index\|email\|id>` |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| Per-invocation account pin | Forces one account for a single wrapper session (ephemeral, fail-hard, never touches the persisted `switch` pin) | `codex-multi-auth-codex --account <index\|email\|id>` |
| Per-invocation account pin | Forces one account for a single forwarded Codex invocation (ephemeral, fail-hard, never touches the persisted `switch` pin) | `codex-multi-auth-codex --account <index\|email\|id>` |
🤖 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/features.md` at line 39, Update the “Per-invocation account pin” row in
the documentation table so it describes a single forwarded Codex invocation
rather than a broader wrapper session. Keep the wording aligned with the
launcher/runtime contract used by codex-multi-auth-codex and its --account flag,
and make clear that the pin is ephemeral for one run only and does not persist
beyond that invocation.

Source: Coding guidelines

Follow-up to the code review of #624:

- Add proxy tests that exercise the real env-consumption branch
  (CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX) with no option, and option-over-env
  precedence (also proving a forced index of 0 is honored) — the exact
  mechanism that must survive the launcher -> detached app-helper boundary.
- Add a launcher test that drives the detached app-helper path (bare
  interactive TUI) and asserts the resolved index reaches the helper process
  env, via a gated marker in the fixture proxy stub.
- Add launcher tests for the email, account-id, --account=<v>, and
  CODEX_MULTI_AUTH_FORCE_ACCOUNT env-var selectors, plus flag-wins-over-env.
- Scrub CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX from the background quota-refresh
  child's env so a management command can never inherit a per-run pin.
- Simplify the proxy option/env guard to `??` (null and undefined both defer
  to the env) and document the tri-state.
- Add the now-required forcedAccountIndex to the rotation-proxy-state test
  helper.
- Document that an all-digit selector is always treated as a 1-based index
  (numeric account ids must be selected by index or email).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ndycode

ndycode commented Jul 9, 2026

Copy link
Copy Markdown
Owner Author

Code review addressed (commit d488f4e)

An independent review flagged 2 Medium (both test-coverage) + several Low/Nit. All actionable items are fixed:

Finding Severity Resolution
Env→proxy consumption path proven only by stubs Medium Added proxy tests exercising the real CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX env branch (no option) and option-over-env precedence (also proves forced index 0 is honored)
App-helper/interactive-TUI path not exercised end-to-end Medium Added a launcher test driving the bare interactive-TUI → detached app-helper path, asserting the resolved index reaches the helper process env (gated marker in the fixture stub)
Email / account-id / --account= / env-var selectors untested Low Added parametrized launcher tests for all four selector sources + flag-wins-over-env
Forced-pin env leaks into background forecast child Low Scrub CODEX_MULTI_AUTH_FORCE_ACCOUNT_INDEX from that child's env
stateInit() test helper missing required field Low Added forcedAccountIndex: null
All-digit account id can't be selected by id Low Documented the index-precedence limitation in commands.md
!== undefined env-fallback foot-gun Nit Switched to ?? (both null and undefined defer to env)
Positional-index drift; proxy-disabled-before-selector error ordering Low/Nit Left as-is per reviewer (pre-existing / defensible)

Verification (Windows, --maxWorkers=1)

  • npm run build, npm run lint (ts + scripts), npm run typecheck, typecheck:scripts — all clean
  • test/documentation.test.ts — 26/26
  • New/changed suites: runtime-rotation-proxy.test.ts, codex-bin-wrapper.test.ts, rotation-proxy-state.test.ts — all green (14 new #623 tests total)
  • Full suite: 5030 passed, 3 skipped, 1 failed — the single failure is test/ci-workflows.test.ts, which fails identically on clean main (a .github/workflows YAML assertion this PR does not touch). Zero new failures.

@ndycode
ndycode merged commit 9fb802f into main Jul 9, 2026
1 of 2 checks passed
ndycode pushed a commit that referenced this pull request Jul 9, 2026
Release the per-invocation --account account-forcing feature (#623/#624).
Bumps package version, promotes CHANGELOG [Unreleased] -> [2.4.0], adds
docs/releases/v2.4.0.md, and updates the README/docs-portal current-stable
links and the AGENTS.md version header.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feature] For codex-multi-auth-codex, force specific account / pool

1 participant