Add runtime rotation proxy#438
Conversation
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughadds an opt-in runtime rotation proxy and a new cli surface Changes
Sequence Diagram(s)sequenceDiagram
participant client as client
participant proxy as runtime-rotation-proxy
participant acctmgr as account-manager
participant oauth as oauth-refresh
participant upstream as responses-upstream
client->>proxy: POST /responses (wrapper token)
proxy->>proxy: validate wrapper auth
proxy->>proxy: parse model & session key
proxy->>acctmgr: select account (affinity/preference)
acctmgr->>acctmgr: check cooldown & token validity
alt needs refresh
acctmgr->>oauth: queued refresh
oauth-->>acctmgr: refreshed token or error
end
proxy->>upstream: forward request with account auth
alt success stream
upstream-->>proxy: stream response
proxy-->>client: stream response (strip hop-by-hop headers)
proxy->>acctmgr: record success & update observability
else transient errors (429/401/5xx/network)
upstream-->>proxy: error / retry-after
proxy->>acctmgr: mark cooldown / rotate
proxy->>acctmgr: try next account
proxy->>upstream: retry forwarding
else pool exhausted
proxy-->>client: json pool-exhausted (retry_after_ms)
end
estimated code review effort🎯 4 (complex) | ⏱️ ~60 minutes specific review flagsmissing regression tests:
windows edge cases:
concurrency risks:
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
✨ Simplify code
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 18
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
test/schemas.test.ts (1)
33-77: 🧹 Nitpick | 🔵 Trivialno negative-type regression for
codexRuntimeRotationProxy.
test/schemas.test.ts:36only exercises the happy path. the schema allows any truthy value today but if someone accidentally widens it toz.unknown()orz.any()the tests stay green. add aPluginConfigSchema.safeParse({ codexRuntimeRotationProxy: "yes" }).success === falseassertion alongside the existingcodexModewrong-type case so schema drift on this new flag gets caught.as per coding guidelines
test/**: tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/schemas.test.ts` around lines 33 - 77, Add a negative-type test for codexRuntimeRotationProxy: in test/schemas.test.ts add an assertion that PluginConfigSchema.safeParse({ codexRuntimeRotationProxy: "yes" }).success is false (similar to the existing codexMode wrong-type case) so the schema rejects string/invalid types; ensure you reference PluginConfigSchema and codexRuntimeRotationProxy and keep the test deterministic with vitest as the other assertions.docs/reference/settings.md (1)
139-150:⚠️ Potential issue | 🟡 Minorenv override listed, but the
codexRuntimeRotationProxypluginConfig key is not documented in the backend controls table.
docs/reference/settings.md:188advertisesCODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXYas a common operator override, but the Rotation & Quota table atdocs/reference/settings.md:139-149never listscodexRuntimeRotationProxy(defaultfalse) as a setting, nor is there any explanation of what the proxy does, its precedence vs. env, or an upgrade note. operators reading this page in isolation will have no idea what the env var toggles.as per coding guidelines
docs/**: keep README, SECURITY, and docs consistent with actual CLI flags and workflows. whenever behavior changes, require updated upgrade notes and mention new npm scripts.proposed additions
### Rotation & Quota | Key | Default | Effect | | --- | --- | --- | +| `codexRuntimeRotationProxy` | `false` | Launch a localhost Responses proxy so forwarded Codex sessions rotate accounts between requests. Toggled via `codex auth rotation enable|disable`. | | `preemptiveQuotaEnabled` | `true` | Defer requests before remaining quota is critically low |Also applies to: 188-188
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/reference/settings.md` around lines 139 - 150, Add documentation for the codexRuntimeRotationProxy pluginConfig key and the corresponding environment variable CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY: update the Rotation & Quota table to include `codexRuntimeRotationProxy` (default `false`), add a short description of what the proxy does (e.g., routes rotation decisions through a runtime proxy), specify precedence rules (pluginConfig overrides env unless explicitly documented otherwise), and include a brief upgrade note describing behavioral changes and any npm/CLI implications; reference the exact symbols `codexRuntimeRotationProxy` and `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY` so operators can find the mapping and precedence.test/codex-manager-cli.test.ts (1)
944-969:⚠️ Potential issue | 🟡 Minoradd a companion regression for
state: "empty"with a windows-style storage path.this new case covers
intentional-reset, but the adjacentemptybranch is not explicitly asserted here. please add oneauth listregression that usesstate: "empty"and ac:/...path to lock behavior for windows path formatting too. seetest/codex-manager-cli.test.ts:946andlib/storage.ts:1317.proposed test add
+ it("prints empty account status for auth list when storage is missing (windows path)", async () => { + loadAccountsMock.mockResolvedValueOnce(null); + getStoragePathMock.mockReturnValue("C:/mock/openai-codex-accounts.json"); + inspectStorageHealthMock.mockResolvedValueOnce({ + state: "empty", + path: "C:/mock/openai-codex-accounts.json", + resetMarkerPath: "C:/mock/openai-codex-accounts.json.intentional-reset", + walPath: "C:/mock/openai-codex-accounts.json.wal", + hasResetMarker: false, + hasWal: false, + details: "storage file is missing", + }); + const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + const { runCodexMultiAuthCli } = await import("../lib/codex-manager.js"); + + const exitCode = await runCodexMultiAuthCli(["auth", "list"]); + + expect(exitCode).toBe(0); + expect(logSpy).toHaveBeenCalledWith(expect.stringContaining("No accounts configured")); + expect(logSpy).toHaveBeenCalledWith("Storage: C:/mock/openai-codex-accounts.json"); + expect(logSpy).toHaveBeenCalledWith("Storage health: empty"); + });as per coding guidelines,
test/**: “tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior.”🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/codex-manager-cli.test.ts` around lines 944 - 969, Add a new Vitest regression test alongside the existing "prints empty account status for auth list" that mocks inspectStorageHealthMock to return state: "empty" and a Windows-style path (e.g. "c:\\mock\\openai-codex-accounts.json"), mock loadAccountsMock to resolve to null, call runCodexMultiAuthCli(["auth","list"]) and assert exitCode 0, that console.log was called with the empty-account message and the Windows storage path output, and that setStoragePathMock was called with null; use the same mocking helpers (loadAccountsMock, inspectStorageHealthMock, setStoragePathMock) and runCodexMultiAuthCli to ensure Windows path formatting in lib/storage.ts is covered.scripts/codex.js (1)
389-449: 🧹 Nitpick | 🔵 Trivialruntime proxy is spawned inside the unsupported-model retry loop — up to 4 server lifecycles per invocation.
scripts/codex.js:394-446retries the whole forward (includingcreateRuntimeRotationProxyContextIfEnabled) up to four times for model-fallback recovery. each retry starts a fresh node http server on an ephemeral port and tears it down. not broken, but it amplifies any listen/close race, and on windows it also churns the shadow CODEX_HOME tmpdir cleanup path that's already racy underEBUSY.consider hoisting the proxy context above the retry loop and only swapping
currentArgson retry. scoped to this PR, at minimum add a comment documenting that each attempt spins a new server so the next reader doesn't chase a port leak.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/development/CONFIG_FIELDS.md`:
- Line 204: Update the docs table row for the
CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY environment variable to list the
accepted/explicit values (e.g., "1|true") so operators know the exact opt-in
tokens; ensure the documented values match the parsing/validation logic in
lib/config.ts and the expectations asserted in test/config.test.ts to keep
behavior consistent across code, tests, and release notes.
In `@docs/features.md`:
- Line 27: Update the Runtime rotation proxy capability row in docs/features.md
to indicate it is opt-in and disabled by default; change the description for the
"Runtime rotation proxy" row to append a short note like "(opt-in, disabled by
default via codexRuntimeRotationProxy=false)" so readers won’t assume it’s
enabled for all forwarded sessions — refer to the capability row text for
"Runtime rotation proxy" and the configuration symbol codexRuntimeRotationProxy
in lib/config.ts when making this wording tweak.
In `@docs/reference/commands.md`:
- Line 61: The table row contains unescaped pipe characters in the command
string `codex auth rotation enable|disable|status`, which breaks the Markdown
table; fix it by escaping each pipe (e.g., change to `codex auth rotation
enable\|disable\|status`) or replace pipes with the HTML entity `|` within
that table cell so the row renders as a single cell; update the line containing
the `codex auth rotation enable|disable|status` command accordingly.
In `@docs/releases/v1.3.1.md`:
- Around line 26-31: Update the Runtime Rotation Proxy release notes to
explicitly reference the regression tests and related docs changes: mention the
CLI flags/flags `codexRuntimeRotationProxy`,
`CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=1`, and commands `codex auth rotation
enable|disable|status`, and add explicit test file references such as
test/runtime-rotation-proxy.test.ts and test/codex-bin-wrapper.test.ts (include
line or section pointers) so readers can verify coverage for concurrency and
Windows edge cases; also update docs/upgrade notes to reflect the new behavior
and add the new npm script entries referenced by this feature in the docs to
keep README/security/docs consistent.
In `@lib/codex-manager/commands/rotation.ts`:
- Line 57: The code silently calls deps.setStoragePath(null) in rotation.ts
before loading accounts, which mutates shared state for a read-only/status
subcommand; either remove that call, or scope it by saving and restoring the
previous value (e.g., const prev = deps.getStoragePath?.();
deps.setStoragePath(null); ...deps.setStoragePath(prev);) or replace it by
passing an explicit local storage override to the account-loading call instead
of mutating deps; also add a one-line comment explaining why a reset is required
if you choose to keep it.
- Around line 55-68: The status output in printRotationStatus currently uses
getCodexRuntimeRotationProxy(config) to derive the displayed "Runtime rotation
proxy" state but the runtime actually resolves env-first (per scripts/codex.js);
update printRotationStatus to compute the effective state using the same
precedence: check the CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY env (use a shared
parseBooleanEnv helper hoisted out of formatEnvOverride) and if unset fall back
to getCodexRuntimeRotationProxy(config), then log that effective value as the
headline; keep formatEnvOverride for showing overrides but ensure both functions
call the same parseBooleanEnv utility so the reported state matches runtime
behavior.
In `@lib/codex-manager/help.ts`:
- Around line 23-24: Help output for the "codex auth rotation" subcommand only
shows "status" and omits the "enable" and "disable" subcommands; update the help
text in lib/codex-manager/help.ts (the array/section that contains the
"Diagnostics:" entry and the "codex auth rotation status" line) to either list
"codex auth rotation enable" and "codex auth rotation disable" alongside
"status" or replace the single-line entry with a concise pointer such as "codex
auth rotation (enable|disable|status) — see 'codex auth rotation --help' so
operators can discover all toggles without editing pluginConfig by hand.
In `@lib/runtime-rotation-proxy.ts`:
- Around line 547-557: The shared RuntimeRotationProxyStatus object is mutated
concurrently across overlapping handleRequest executions causing non-atomic
pairs (lastError/lastAccountIndex) to interleave; change handleRequest to
capture per-request local counters/state (e.g., localTotalRequestsDelta,
localUpstreamRequestsDelta, localRetries, localRotations, localStreamsStarted,
and a local lastError/lastAccountIndex pair) and apply them back to the shared
status only once (atomic reduce) at the end of the request, or alternatively
store lastError and lastAccountIndex together (as a single object/tuple) so
getStatus() reads a consistent pair; update lib/runtime-rotation-proxy.ts around
the status initialization and handleRequest sites to use the per-request
snapshot-and-reduce approach and add a Promise.all-based concurrency test to
ensure no interleaving regressions.
- Around line 683-712: The 401 (upstream.status === HTTP_STATUS.UNAUTHORIZED)
branch in lib/runtime-rotation-proxy.ts is inconsistent with the 5xx branch
because it records failures and cool-downs but does not call
accountManager.refundToken(refreshed.account, context.family, context.model);
decide and fix by either (A) adding the same refundToken call in the 401 branch
(and incrementing status/retries/rotations as done for 5xx) so token bookkeeping
is consistent, or (B) explicitly document with an inline comment in the
UNAUTHORIZED branch (and likewise in the earlier 429 branch) that no refund is
performed because quota was consumed upstream/this response legitimately used
the token; update both branches (upstream.status === HTTP_STATUS.UNAUTHORIZED
and the 429 handling) accordingly to avoid future confusion.
- Around line 105-125: The createOutboundHeaders function contains a redundant
branch that re-sets OPENAI_HEADERS.ACCOUNT_ID to accountId when
account.accountId !== accountId; remove that no-op guard and assignment (the
second headers.set(OPENAI_HEADERS.ACCOUNT_ID, accountId) and its surrounding if
block) since ACCOUNT_ID is already set earlier in createOutboundHeaders and
accountId is resolved via resolveAccountId; leave the initial headers.set(...)
and other header logic intact.
- Around line 127-133: Replace the insecure === comparisons in
isAuthorizedClient with a constant-time comparison using Node's
crypto.timingSafeEqual: when extracting the bearer token (bearerMatch) or the
x-api-key header, convert both the candidate and clientApiKey to Buffers (e.g.,
Buffer.from(..., "utf8")), first check lengths and only call timingSafeEqual
when lengths match, otherwise treat as non-match; update isAuthorizedClient to
perform this buffered, length-checked timing-safe comparison for both the bearer
token branch and the x-api-key branch.
- Around line 144-150: readRequestBody currently accumulates the entire request
with no cap; change readRequestBody to enforce a 64 * 1024 * 1024 byte limit by
summing chunk lengths as it reads and throw or return a distinct error when
exceeded; ensure the upstream handler in the same module catches that error and
responds with 413 (use HTTP_STATUS.PAYLOAD_TOO_LARGE if you add
PAYLOAD_TOO_LARGE = 413 to the HTTP_STATUS enum, or use the literal 413), and
add a vitest in runtime-rotation-proxy.test.ts that posts a body larger than the
limit and asserts the response status is 413 to cover the error path.
In `@scripts/codex.js`:
- Around line 1036-1050: The parseRuntimeRotationProxyEnv function currently
logs an error every time it sees a malformed
CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY, causing repeated warnings across
invocations; add a module-scoped "alreadyWarnedInvalidRuntimeRotationProxy"
boolean and only call console.error the first time (set the flag true after
logging) so subsequent calls to
parseRuntimeRotationProxyEnv/isRuntimeRotationProxyEnabled are silent; update
references to parseRuntimeRotationProxyEnv (and any callers like
isRuntimeRotationProxyEnabled) to use the same guarded behavior so the warning
is emitted once per process.
- Around line 407-413: The current early return when
createRuntimeRotationProxyContextIfEnabled yields null makes proxy failures
fatal; change createRuntimeRotationProxyContextIfEnabled (and its callers in
scripts/codex.js) to return the original compatibility context on
proxy-unavailable paths and only cause process exit when
CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY_STRICT=1 is set; remove the return 1
branch at the call site and instead log a single warning when falling back; add
two vitest regressions in test/codex-bin-wrapper.test.ts that stub
startRuntimeRotationProxy to throw and assert the wrapper still forwards to the
real codex (fallback), and a sibling test that sets
CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY_STRICT=1 and asserts the process exits
with code 1.
- Around line 1099-1125: The table-detection regex used in
rewriteTopLevelModelProvider (and the mirrored one in
removeRuntimeRotationProviderBlock) doesn't match TOML array-of-tables like
[[server]], causing model_provider to be appended into the wrong section; update
the regex to detect both single- and double-bracket headers (e.g. use a pattern
that allows one or two opening and closing brackets such as
/^\s*\[{1,2}[^\]]+\]{1,2}\s*$/) and apply this change in both
rewriteTopLevelModelProvider and removeRuntimeRotationProviderBlock so the
rewrittenLine is injected before any table/array-of-tables header; additionally
add a regression test that uses a file with a leading [[profiles.foo]] header to
assert model_provider is inserted at the top-level (not nested) when calling
rewriteTopLevelModelProvider/rewriteConfigTomlForRuntimeRotationProxy.
In `@test/codex-bin-wrapper.test.ts`:
- Around line 161-193: The fixture module created by
createRuntimeRotationProxyFixtureModule returns getStatus: () => ({}) which
doesn't match the RuntimeRotationProxyStatus contract and hides fallback
behavior; update the generated startRuntimeRotationProxy export to return a
status object containing startedAt, totalRequests, upstreamRequests, retries,
rotations, streamsStarted, lastError, and lastAccountIndex (use reasonable
defaults/zeroes and strings/nulls) and ensure the returned object shape matches
the real implementation in lib/runtime-rotation-proxy.ts (and usage in
test/runtime-rotation-proxy.test.ts); additionally add a negative test case that
simulates the module failing to start (e.g., make startRuntimeRotationProxy
throw or remove the module) to exercise the scripts/codex.js fallback path so
the regression is locked in.
In `@test/codex-manager-status-command.test.ts`:
- Around line 69-88: The test only covers restoreReason "intentional-reset" and
drops the return-value assertion; add tests exercising the other restoreReason
branches ("empty-storage" and "missing-storage") by creating deps via
createStatusDeps with loadAccounts returning version 3, empty accounts and
restoreReason set to each value, and mock inspectStorageHealth to return
undefined so those reasons map to the "empty" state; call runStatusCommand(deps)
and assert both that deps.logInfo was called with "No accounts configured." (or
the expected empty-state message) and that the command result equals 0 (to match
sibling tests).
In `@test/runtime-rotation-proxy.test.ts`:
- Around line 138-360: Add tests to cover the three missing regression cases and
tighten the pool-exhausted assertion: 1) Add a test that sends x-api-key:
runtime-secret (no Authorization header) against startRuntimeRotationProxy to
exercise the branch that checks headers.get("x-api-key") === clientApiKey. 2)
Add a concurrency test that fires N postResponses concurrently against
startProxy and then assert
accountManager.consumeToken/refundToken/markRateLimitedWithReason behavior and
proxy.getStatus() counters to ensure no negative budgets or duplicate account
picks. 3) Add a token-refresh race test that uses startProxy with accounts whose
expiresAt < now and issues two simultaneous requests so
ensureFreshAccessToken/queuedRefresh dedupes (assert saveAccountsMock was called
exactly once and both responses succeed). Finally, in the pool-exhausted test
replace the loose retry_after_ms > 0 check with an exact equality/assertion
against accountManager.getMinWaitTimeForFamily(family, model) to validate
writePoolExhausted returns the correct value; use helpers postResponses,
startProxy/startRuntimeRotationProxy and reference AccountManager and
saveAccountsMock to locate relevant mocks.
---
Outside diff comments:
In `@docs/reference/settings.md`:
- Around line 139-150: Add documentation for the codexRuntimeRotationProxy
pluginConfig key and the corresponding environment variable
CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY: update the Rotation & Quota table to
include `codexRuntimeRotationProxy` (default `false`), add a short description
of what the proxy does (e.g., routes rotation decisions through a runtime
proxy), specify precedence rules (pluginConfig overrides env unless explicitly
documented otherwise), and include a brief upgrade note describing behavioral
changes and any npm/CLI implications; reference the exact symbols
`codexRuntimeRotationProxy` and `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY` so
operators can find the mapping and precedence.
In `@test/codex-manager-cli.test.ts`:
- Around line 944-969: Add a new Vitest regression test alongside the existing
"prints empty account status for auth list" that mocks inspectStorageHealthMock
to return state: "empty" and a Windows-style path (e.g.
"c:\\mock\\openai-codex-accounts.json"), mock loadAccountsMock to resolve to
null, call runCodexMultiAuthCli(["auth","list"]) and assert exitCode 0, that
console.log was called with the empty-account message and the Windows storage
path output, and that setStoragePathMock was called with null; use the same
mocking helpers (loadAccountsMock, inspectStorageHealthMock, setStoragePathMock)
and runCodexMultiAuthCli to ensure Windows path formatting in lib/storage.ts is
covered.
In `@test/schemas.test.ts`:
- Around line 33-77: Add a negative-type test for codexRuntimeRotationProxy: in
test/schemas.test.ts add an assertion that PluginConfigSchema.safeParse({
codexRuntimeRotationProxy: "yes" }).success is false (similar to the existing
codexMode wrong-type case) so the schema rejects string/invalid types; ensure
you reference PluginConfigSchema and codexRuntimeRotationProxy and keep the test
deterministic with vitest as the other assertions.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: c4aeb4c6-4189-4828-a856-ab980cbb4e4d
📒 Files selected for processing (24)
README.mddocs/configuration.mddocs/development/CONFIG_FIELDS.mddocs/features.mddocs/reference/commands.mddocs/reference/settings.mddocs/releases/v1.3.1.mdlib/codex-manager.tslib/codex-manager/commands/rotation.tslib/codex-manager/commands/status.tslib/codex-manager/help.tslib/config.tslib/index.tslib/runtime-rotation-proxy.tslib/schemas.tsscripts/codex-routing.jsscripts/codex.jstest/codex-bin-wrapper.test.tstest/codex-manager-cli.test.tstest/codex-manager-rotation-command.test.tstest/codex-manager-status-command.test.tstest/plugin-config.test.tstest/runtime-rotation-proxy.test.tstest/schemas.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (README.md)
Project storage paths should use
~/.codex/multi-auth/as the default root directory, with per-project accounts stored under~/.codex/multi-auth/projects/<project-key>/Use
CODEX_MULTI_AUTH_DIRenvironment variable to allow users to override the default settings/accounts root directoryRespect
CODEX_MODE=0/1environment variable to disable/enable Codex mode at runtimeImplement
CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=0/1to allow users to opt in/out of live Responses proxy rotation for forwarded Codex sessionsSupport
CODEX_TUI_V2=0/1to disable/enable TUI v2, andCODEX_TUI_COLOR_PROFILEandCODEX_TUI_GLYPHSfor TUI customizationAllow
CODEX_AUTH_FETCH_TIMEOUT_MSandCODEX_AUTH_STREAM_STALL_TIMEOUT_MSenvironment variables to override request and stream timeout defaultsDashboard navigation should support hotkeys: Up/Down for movement, Enter to select, 1-9 for quick switch, / for search, ? for help toggle, and Q for back/cancel
Account details view should support hotkeys: S to set current account, R to refresh/re-login, E to enable/disable, and D to delete
Files:
lib/codex-manager/help.tstest/schemas.test.tsscripts/codex-routing.jslib/index.tstest/codex-manager-status-command.test.tslib/schemas.tslib/config.tslib/codex-manager.tslib/codex-manager/commands/status.tstest/codex-manager-cli.test.tstest/plugin-config.test.tstest/codex-bin-wrapper.test.tstest/runtime-rotation-proxy.test.tstest/codex-manager-rotation-command.test.tslib/codex-manager/commands/rotation.tsscripts/codex.jslib/runtime-rotation-proxy.ts
lib/**
⚙️ CodeRabbit configuration file
focus on auth rotation, windows filesystem IO, and concurrency. verify every change cites affected tests (vitest) and that new queues handle EBUSY/429 scenarios. check for logging that leaks tokens or emails.
Files:
lib/codex-manager/help.tslib/index.tslib/schemas.tslib/config.tslib/codex-manager.tslib/codex-manager/commands/status.tslib/codex-manager/commands/rotation.tslib/runtime-rotation-proxy.ts
test/**
⚙️ CodeRabbit configuration file
tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior. reject changes that mock real secrets or skip assertions.
Files:
test/schemas.test.tstest/codex-manager-status-command.test.tstest/codex-manager-cli.test.tstest/plugin-config.test.tstest/codex-bin-wrapper.test.tstest/runtime-rotation-proxy.test.tstest/codex-manager-rotation-command.test.ts
docs/**
⚙️ CodeRabbit configuration file
keep README, SECURITY, and docs consistent with actual CLI flags and workflows. whenever behavior changes, require updated upgrade notes and mention new npm scripts.
Files:
docs/reference/settings.mddocs/features.mddocs/development/CONFIG_FIELDS.mddocs/releases/v1.3.1.mddocs/reference/commands.mddocs/configuration.md
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:39:51.027Z
Learning: Implement reliability behavior: disable whole-pool replay by default when every account is rate-limited, use bounded outbound request budgets, trigger cooldown on repeated cross-account 5xx bursts, and stagger proactive refresh
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:39:51.027Z
Learning: Maintain project-scoped account storage and isolation, allowing per-project account configurations while respecting the global multi-auth pool
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:39:51.027Z
Learning: Implement health-aware account selection with automatic failover across the multi-account OAuth pool
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:39:51.027Z
Learning: Support multi-auth probe visibility and runtime counters in `codex auth status` and `codex auth report` output for operational observability
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:02.283Z
Learning: Plugin runtime config source selection must follow resolution precedence: (1) Unified settings pluginConfig from settings.json when present and valid, (2) Fallback file config from CODEX_MULTI_AUTH_CONFIG_PATH when unified settings absent/invalid, (3) Hardcoded defaults. Environment variables override individual runtime settings after source selection.
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:02.283Z
Learning: Use stable environment overrides (CODEX_MULTI_AUTH_DIR, CODEX_MULTI_AUTH_CONFIG_PATH, CODEX_MODE, CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY, CODEX_TUI_V2, CODEX_TUI_COLOR_PROFILE, CODEX_TUI_GLYPHS, CODEX_AUTH_FETCH_TIMEOUT_MS, CODEX_AUTH_STREAM_STALL_TIMEOUT_MS) for common operational workflows
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:02.283Z
Learning: Advanced and internal environment overrides (CODEX_MULTI_AUTH_SYNC_CODEX_CLI, CODEX_MULTI_AUTH_REAL_CODEX_BIN, CODEX_MULTI_AUTH_BYPASS, CODEX_CLI_ACCOUNTS_PATH, CODEX_CLI_AUTH_PATH, refresh lease tuning variables) should only be used during debugging, controlled benchmarking, or maintainer workflows
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:20.558Z
Learning: Avoid manually editing lease/state files while the CLI is running, as cross-process refresh coordination relies on these files
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:20.558Z
Learning: When enabling `backgroundResponses` compatibility switch, test one known `background: true` request end to end before rolling across shared automation
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:20.558Z
Learning: Leave `backgroundResponses` disabled for existing stateless pipelines that do not intentionally send `background: true` requests
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:20.558Z
Learning: Enable `backgroundResponses` only for callers that need stateful background responses and can accept forced `store=true`, preserved input item IDs, and loss of stateless-only defaults such as fast-session trimming
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:29.527Z
Learning: Support project-scoped account pools for repo-specific account state separation
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:29.527Z
Learning: Implement linked-worktree identity sharing so the same repository can share account state across worktrees
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:29.527Z
Learning: Implement account dedupe and identity normalization to avoid duplicate saved account rows
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:29.527Z
Learning: Support backup and WAL recovery for safer persistence when local writes are interrupted or partially applied
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:29.527Z
Learning: Implement runtime rotation proxy to rotate managed accounts between Response requests without restarting sessions
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:29.527Z
Learning: Implement plugin runtime that uses the same account pool for request transformation, token refresh, retry handling, and capability-aware account selection
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:40.542Z
Learning: Use canonical command family `codex auth ...` for primary operations with compatibility alias support for `codex multi auth ...`, `codex multi-auth ...`, and `codex multiauth ...`
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:40.542Z
Learning: Use `codex auth login --manual` or `codex auth login --no-browser` to force manual callback flow instead of launching a browser
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:40.542Z
Learning: Set `CODEX_AUTH_NO_BROWSER=1` to suppress browser launch in automation/headless sessions; false-like values such as `0` and `false` do not disable browser launch by themselves
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:40.542Z
Learning: In non-TTY/manual shells, pass the full redirect URL on stdin for `codex auth login --manual`, for example: `echo 'http://127.0.0.1:1455/auth/callback?code=...' | codex auth login --manual`
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:40.542Z
Learning: Implement non-TTY fallback for auth flows to degrade to deterministic text behavior in non-TTY or host-managed sessions (including `CODEX_TUI=1`, `CODEX_DESKTOP=1`, `TERM_PROGRAM=codex`, or `ELECTRON_RUN_AS_NODE=1`)
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:40.542Z
Learning: In non-TTY fallback, `codex auth login` should default to add-account mode, skip the extra 'add another account' prompt, and auto-pick the default workspace selection when a follow-up choice is needed
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:40.542Z
Learning: Include `--explain` flag in `forecast` and `report` commands to include reasoning details (forecast text/JSON, report text)
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:40.542Z
Learning: Implement `codex auth rotation` command to manage the opt-in runtime Responses proxy with `enable`, `disable`, and `status` subcommands
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:40.542Z
Learning: When `codex auth rotation enable` is invoked, persist `codexRuntimeRotationProxy=true`; when `disable` is invoked, persist `codexRuntimeRotationProxy=false`
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:40.542Z
Learning: Implement `codex auth verify` as a unified entry point for installation self-tests with `--paths`, `--flagged`, and `--all` flags; `verify-flagged` should continue to work as a back-compat alias
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:58.439Z
Learning: Use environment variable `CODEX_MULTI_AUTH_DIR` to change the settings file root directory from the default `~/.codex/multi-auth/`.
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:58.439Z
Learning: Use stable environment overrides like `CODEX_MODE`, `CODEX_TUI_V2`, `CODEX_TUI_COLOR_PROFILE`, `CODEX_TUI_GLYPHS`, `CODEX_AUTH_FETCH_TIMEOUT_MS`, and `CODEX_AUTH_STREAM_STALL_TIMEOUT_MS` for common operator configuration.
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:58.439Z
Learning: After making changes to settings, run validation commands: `codex auth status`, `codex auth check`, and `codex auth forecast --live` to ensure configuration is correct.
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:40:58.439Z
Learning: For most environments, enable: smart sort (`menuSortEnabled`), auto-fetch limits (`menuAutoFetchLimits`), live sync (`liveAccountSync`), session affinity (`sessionAffinity`), preemptive quota deferral (`preemptiveQuotaEnabled`), and proactive refresh guardian (`proactiveRefreshGuardian`).
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:41:00.614Z
Learning: Run npm build as part of the validation process for releases
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T13:41:00.614Z
Learning: Run test suite including model-map.test.ts, request-transformer.test.ts, codex-bin-wrapper.test.ts, config.test.ts, capability-policy.test.ts, fetch-helpers.test.ts, and index.test.ts as part of release validation
🪛 markdownlint-cli2 (0.22.1)
docs/reference/commands.md
[warning] 61-61: Table column count
Expected: 2; Actual: 4; Too many cells, extra data will be missing
(MD056, table-column-count)
🔇 Additional comments (10)
scripts/codex-routing.js (1)
14-14: lgtm.adds
rotationto the routed auth subcommand allowlist inscripts/codex-routing.js:14, matching the dispatcher branch and the aliases exercised intest/codex-manager-rotation-command.test.ts.lib/schemas.ts (1)
27-27: lgtm.optional boolean at
lib/schemas.ts:27aligns withDEFAULT_PLUGIN_CONFIG.codexRuntimeRotationProxy = falseinlib/config.ts:154-223and flows throughsanitizePluginConfigRecordwithout breaking older configs that omit the field.lib/index.ts (1)
35-35: star-export is safe—no symbol collisions found.the barrel re-export at
lib/index.ts:35was flagged for potential collisions with sibling modules (session-affinity, refresh-guardian, etc.). verification shows all exported symbols use consistent module-specific prefixes:RuntimeRotationProxy*,SessionAffinity*,RefreshGuardian*,RefreshLease*,PreemptiveQuota*,LiveAccountSync*,CircuitBreaker*. no overlapping names like*Optionsorstart*across module boundaries. the export is safe.docs/development/CONFIG_FIELDS.md (1)
47-47: good config field addition for runtime proxy toggle.this matches the new runtime rotation control surface and keeps config docs aligned with runtime wiring (
lib/config.ts:line,lib/codex-manager/commands/rotation.ts:line).based on learnings: use stable environment overrides (
CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY) for common operational workflows.test/codex-manager-cli.test.ts (3)
9-10: good mock addition.
inspectStorageHealthMockis declared cleanly and fits the suite’s existing mock pattern. seetest/codex-manager-cli.test.ts:9.
183-184: mock wiring is correct.binding
inspectStorageHealthin the storage module mock is the right integration point for this cli behavior. seetest/codex-manager-cli.test.ts:183.
661-836: beforeeach reset/default is solid and reduces async bleed risk.resetting and priming
inspectStorageHealthMockhere keeps runs deterministic and avoids cross-test interference when tests execute with overlapping async work. seetest/codex-manager-cli.test.ts:661andtest/codex-manager-cli.test.ts:828.lib/codex-manager.ts (1)
337-3397: rotation command dispatch wiring looks good.the new
auth rotationbranch is cleanly wired and passes the right config/storage deps for enable/disable/status without changing other command flows. reference:lib/codex-manager.ts:337.lib/config.ts (1)
806-814: runtime rotation config resolution is correctly implemented.the getter and explain metadata follow the expected env/config/default precedence and expose the new stable override cleanly. reference:
lib/config.ts:806.Also applies to: 1628-1632
test/plugin-config.test.ts (1)
1117-1131: good regression coverage for setting precedence.this test directly locks in
env > config > defaultbehavior for runtime rotation proxy and prevents future precedence regressions. reference:test/plugin-config.test.ts:1117.
There was a problem hiding this comment.
Actionable comments posted: 16
♻️ Duplicate comments (6)
docs/features.md (1)
27-27:⚠️ Potential issue | 🟡 Minorcapability row still reads as always-on; past review already asked for the opt-in qualifier.
this row in
docs/features.md:27was flagged on a prior commit and the wording hasn't moved — readers skimming the capability table will still assume runtime rotation is live on every forwarded session.codexRuntimeRotationProxydefaults tofalseinlib/config.ts, so the entry needs the opt-in / default-off qualifier to stay truthful and match thedocs/configuration.mddescription.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/features.md` at line 27, Update the features table row to indicate runtime rotation is opt-in by adding a qualifier like "opt-in / disabled by default (codexRuntimeRotationProxy = false)" and/or "requires `codex auth rotation enable`" so it matches the default in lib/config.ts and the docs/configuration.md; reference the feature name codexRuntimeRotationProxy and the existing CLI hint `codex auth rotation enable` (the row currently "Runtime rotation proxy") when editing docs/features.md to make the default-off behavior explicit.docs/reference/commands.md (1)
61-61:⚠️ Potential issue | 🟡 Minorescape the pipe characters in the rotation command table row.
line 61 uses raw
|inside a table cell, so markdown parsers split the row into extra columns. this makes the advanced command table render incorrectly even though the command set is valid (lib/codex-manager/commands/rotation.ts:294-313).proposed docs fix
-| `codex auth rotation enable|disable|status|bind-app|unbind-app` | Manage the opt-in runtime Responses proxy for live Codex account rotation | +| `codex auth rotation enable\|disable\|status\|bind-app\|unbind-app` | Manage the opt-in runtime Responses proxy for live Codex account rotation |🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/reference/commands.md` at line 61, The table row contains unescaped pipe characters in the cell holding the rotation subcommands (the string shown as codex auth rotation enable|disable|status|bind-app|unbind-app) which breaks the Markdown table; edit the commands.md table row to escape each pipe as \| so the entire command remains a single cell. Locate the row displaying the rotation commands (ref: rotation.ts command set in lib/codex-manager/commands/rotation.ts for the exact subcommand list) and replace each '|' with '\|' in that table cell so the Markdown renders correctly.test/codex-manager-status-command.test.ts (1)
116-135:⚠️ Potential issue | 🟡 Minorcover the remaining
restorereasonbranches in status tests.line 116 only exercises
"intentional-reset".lib/codex-manager/commands/status.ts:85-88still has explicit"empty-storage"and"missing-storage"paths that can regress silently. also keep the exit-code assertion parity (result === 0) in this block.proposed test follow-up
it("prints intentional reset state from empty storage metadata", async () => { const deps = createStatusDeps({ @@ }); - await runStatusCommand(deps); + const result = await runStatusCommand(deps); + expect(result).toBe(0); @@ expect(deps.logInfo).toHaveBeenCalledWith( "Storage health: intentional-reset", ); }); + +it.each(["empty-storage", "missing-storage"] as const)( + "treats %s restore metadata as empty health when inspector is unavailable", + async (reason) => { + const deps = createStatusDeps({ + loadAccounts: vi.fn(async () => ({ + version: 3, + activeIndex: 0, + activeIndexByFamily: {}, + accounts: [], + restoreReason: reason, + })), + inspectStorageHealth: undefined, + }); + const result = await runStatusCommand(deps); + expect(result).toBe(0); + expect(deps.logInfo).toHaveBeenCalledWith("No accounts configured."); + expect(deps.logInfo).toHaveBeenCalledWith("Storage health: empty"); + }, +);as per coding guidelines
test/**: “tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior.”🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/codex-manager-status-command.test.ts` around lines 116 - 135, Add deterministic tests for the remaining restoreReason branches ("empty-storage" and "missing-storage") in the same style as the existing "intentional-reset" test: createStatusDeps with loadAccounts returning restoreReason set to "empty-storage" and "missing-storage", call runStatusCommand(deps), assert deps.logInfo was called with the corresponding human message (matching the messages produced in lib/codex-manager/commands/status.ts for those branches) and also assert the command result/exit code equals 0; use the same helpers (createStatusDeps, runStatusCommand, deps.logInfo) so the new tests cover all restoreReason paths and include the result === 0 parity check.test/runtime-rotation-proxy.test.ts (1)
139-348:⚠️ Potential issue | 🟠 Majorthe proxy suite still misses the x-api-key, concurrent-request, and refresh-race regressions.
at
test/runtime-rotation-proxy.test.ts:139-348, the suite only exercises single-request bearer flows. it still leaves three risky paths inlib/runtime-rotation-proxy.tsunpinned: thex-api-keyauth branch, concurrent request handling against shared account state, and queued refresh dedupe when two expired-token requests race. the pool-exhaustion assertion is also still loose (retry_after_ms > 0) instead of checking the exact wait returned by the account manager. please add deterministic vitest cases for those paths before shipping this proxy. as per coding guidelines,test/**: tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior. reject changes that mock real secrets or skip assertions.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/runtime-rotation-proxy.test.ts` around lines 139 - 348, The test suite is missing deterministic regression tests for the x-api-key auth path, concurrent-request account-state updates, refresh-token race dedupe, and a strict pool-exhaustion retry_after_ms assertion; add vitest cases that (1) exercise the x-api-key branch by sending a /responses request with x-api-key instead of Bearer and assert the upstream call contains x-api-key and is authorized via that branch (use startRuntimeRotationProxy or startProxy and postResponses), (2) reproduce concurrent requests against shared AccountManager by issuing parallel postResponses calls (Promise.all) and assert account rotation and accountManager state updates are consistent (reference AccountManager, getAccountByIndex and proxy.getStatus), (3) reproduce two simultaneous expired-token requests that trigger token refresh and ensure the refresh is deduped (simulate upstream responses in createRecordingFetch that return 401/expired then successful token, assert only one refresh attempt and both client responses succeed), and (4) tighten the pool-exhaustion test to assert payload.error.retry_after_ms equals the exact value computed by AccountManager/createStorage (use deterministic createStorage(now, ...) to control timing) rather than just >0; ensure all tests use vitest deterministically, the existing helpers (createRecordingFetch, createStorage, startProxy/startRuntimeRotationProxy, postResponses) and never depend on real secrets or nondeterministic timers.scripts/codex.js (2)
1600-1639:⚠️ Potential issue | 🔴 Criticalproxy startup failures still hard-stop forwarded codex sessions.
at
scripts/codex.js:1604-1639, missing dist output, proxy bind failure, or shadow-home setup failure returnsnull, andscripts/codex.js:444-450then exits 1 instead of forwarding with the already-built compatibility context. that turns an opt-in routing feature into a hard availability dependency for every forwardedexec/app-server/appsession. please fall back tobaseContextby default and reserve the hard stop for an explicit strict override, with a regression intest/codex-bin-wrapper.test.tsfor fallback vs strict mode. based on learnings: supportCODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=0/1to opt in to live Codex Responses routing through the localhost account-rotation proxy.Also applies to: 444-450
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/codex.js` around lines 1600 - 1639, The current createRuntimeRotationProxyContextIfEnabled flow returns null on any proxy startup or shadow-home error which causes caller hard-exit; change it to fall back to returning baseContext (not null) on failures from loadRuntimeRotationProxyModule, proxyModule.startRuntimeRotationProxy, createRuntimeRotationProxyCodexHome, or proxyServer.close cleanup, and only return null (or propagate a fatal error) if an explicit strict mode env/flag is set (implement a new boolean check for CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY that accepts "0"/"1" and a separate strict override like CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY_STRICT); keep createRuntimeRotationAppHelperContext behavior for app commands, ensure baseContext.cleanup is still called on best-effort cleanup, and add/update tests in test/codex-bin-wrapper.test.ts to assert fallback-to-baseContext vs strict-mode null/exit behavior and that opt-in via CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=1 triggers proxy attempts.
1119-1165:⚠️ Potential issue | 🟠 Majorthe toml header matcher still misses
[[...]], somodel_providercan be rewritten into the wrong section.at
scripts/codex.js:1129andscripts/codex.js:1147, both helpers only recognize single-bracket TOML tables. configs that begin with[[profiles.foo]]or any other array-of-tables header will push the injectedmodel_providerunder that table instead of the top level, and the runtime proxy provider will never activate. match one-or-two bracket headers in both places and add a vitest regression intest/codex-bin-wrapper.test.tswith a leading[[...]]block.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/codex.js` around lines 1119 - 1165, The TOML header regex in removeRuntimeRotationProviderBlock and rewriteTopLevelModelProvider only matches single-bracket tables and misses array-of-tables like [[...]], so update both header checks (the /^\s*\[[^\]]+\]\s*$/ tests in removeRuntimeRotationProviderBlock and the isTable test in rewriteTopLevelModelProvider) to match one-or-two-bracket headers (e.g. /^\s*(\[\[|\[)[^\]]+(\]\]|\])\s*$/) so injected model_provider goes at top-level, and add a vitest regression in codex-bin-wrapper.test.ts that includes a leading [[...]] block to verify the behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/codex-manager/commands/rotation.ts`:
- Around line 154-170: formatAppRuntimeHelperStatus currently treats
isProcessAlive(status.pid) as sufficient proof the helper is the active router;
that can be false-positive on Windows when PIDs are reused and a stale
runtime-rotation-app-helper.json remains. Fix by gating liveness on status
freshness: in function formatAppRuntimeHelperStatus call
readAppRuntimeHelperStatus and reject a status if updatedAt or idleExpiresAt is
stale (e.g., older than a safe TTL or idleExpiresAt <= now), and/or require a
persisted instance token from the helper (augment readAppRuntimeHelperStatus to
return instanceToken and validate it) before trusting
isProcessAlive(status.pid); keep formatHelperLastAccount usage but ensure it
does not log sensitive tokens/emails. Add a vitest regression that writes a
stale runtime-rotation-app-helper.json with a reused PID and asserts
formatAppRuntimeHelperStatus reports “not running”; include tests that simulate
Windows EBUSY file contention on writes and 429-like retry behavior for any new
queue logic, and update tests to verify no tokens or emails appear in logs.
In `@lib/runtime-rotation-proxy.ts`:
- Around line 660-672: The loop currently uses accountManager.getAccountCount()
as the retry ceiling which allows a single request to iterate the entire pool;
change the per-request rotation budget to a bounded value (e.g., const
perRequestBudget = Math.min(accountManager.getAccountCount(), 3) or make 3–5
configurable) and replace the while condition to stop after
attemptedIndexes.size >= perRequestBudget while still keeping getAccountCount()
as the absolute safety cap elsewhere; when the loop stops due to hitting this
perRequestBudget, set exhaustionReason = "budget" (instead of "no-account") and
emit the same pool_exhausted path with reason: "budget" so callers/logging know
it was a per-request cap, referencing exhaustionReason, attemptedIndexes,
chooseAccount, accountManager.getAccountCount(), sessionAffinityStore and
context.sessionKey to locate the logic.
- Around line 205-213: The response currently emits a raw email in headers via
headers["x-codex-multi-auth-account-email"] using
sanitizeHeaderValue(accountIdentity.email), which leaks soft PII; remove that
header emission or replace it with a non-reversible derived value (e.g.,
HMAC/SHA256 hash) before setting the header; locate the block that checks
accountIdentity and modifies headers (references: accountIdentity,
sanitizeHeaderValue, headers["x-codex-multi-auth-account-email"]) and either
delete the line that sets the email header or compute and set a hashed/redacted
value instead, keeping index/label/accountId behavior unchanged.
- Around line 722-729: The fetch to upstream in the request handling path lacks
timeouts and the streaming reader has no stall detection; wrap the fetchImpl
call in runtime-rotation-proxy.ts with an AbortController wired to
getFetchTimeoutMs(pluginConfig) so the fetch is aborted on timeout (the existing
catch will handle refund/rotate), and add a stall timer inside
forwardStreamingResponse that resets on each received chunk and aborts the
reader (calling onStreamError()) when getStreamStallTimeoutMs(pluginConfig)
elapses with no data; also add a vitest in test/runtime-rotation-proxy.test.ts
that stubs fetchImpl to never resolve and asserts rotation and pool-exhausted
behavior occurs within the fetch timeout window.
In `@lib/runtime/app-bind.ts`:
- Around line 484-503: spawnRouter currently starts the router with stdio:
"ignore" which discards stdout/stderr while the mac/windows persistent launchers
point their StandardOutPath/StandardErrorPath to state.logPath; change
spawnRouter to open state.logPath in append mode (e.g.
fs.openSync(state.logPath, "a")) and pass stdio: ["ignore", logFd, logFd] (using
the opened fd) when calling spawn so CLI-launched router writes to the same log
file as the persistent agents; ensure the opened fd is a stable integer passed
into spawn and closed when no longer needed.
- Around line 392-406: findAvailablePort currently closes the probe socket
before spawnRouter binds, allowing a TOCTOU race; change the flow in
bindCodexAppRuntimeRotation to perform a bounded retry loop: call
findAvailablePort, invoke spawnRouter with that port, then call
waitForRouterStatus (modify waitForRouterStatus to return a boolean success
flag) and if it returns false retry findAvailablePort+spawnRouter up to N
attempts before failing; ensure spawnRouter and bindCodexAppRuntimeRotation
surface a hard error (instead of returning a "bound" message) when
waitForRouterStatus remains false, and include the waitForRouterStatus result in
the AppBindResult.message so failures are visible.
- Around line 593-602: The current direct writeFile calls that overwrite
paths.configPath, paths.backupPath and paths.statePath in
lib/runtime/app-bind.ts must be replaced with an atomic-write helper: write to a
temp file in the same directory, fsync the file descriptor, fsync the directory,
then rename to the final path (to survive AV/locks and power loss) and handle
transient EPERM/EBUSY by retrying a few times with backoff; update the write at
the bind side (the writeFile for paths.configPath at the spot shown) and the
corresponding unbind writes (the other writeFile sites referenced in your
comment) to use this helper; add a Vitest in test/app-bind.test.ts that
stubs/throws EBUSY for the initial direct write attempt and asserts the
atomic-write helper retries and leaves a valid intact config.toml (i.e., no
truncated file) and that the backup remains intact if rename succeeds.
- Around line 100-116: The removal logic in removeRuntimeRotationProviderBlock
erroneously stops skipping on any section header (e.g.
`[model_providers.codex-multi-auth-runtime-proxy.http_headers]`) instead of
treating subtables as part of the same block; update the stop condition so that
while skipping you only stop when you encounter a new section whose header does
NOT begin with `model_providers.${RUNTIME_ROTATION_PROXY_PROVIDER_ID}` (use a
regex that matches section headers and checks the prefix), keep the rest of the
function unchanged, and add a vitest in test/app-bind.test.ts that supplies a
config containing `[model_providers.${RUNTIME_ROTATION_PROXY_PROVIDER_ID}]` plus
a subtable like
`[model_providers.${RUNTIME_ROTATION_PROXY_PROVIDER_ID}.http_headers]` and
assert the entire group (both the provider block and its subtables) is removed.
- Around line 186-204: restoreConfigTomlFromAppBind currently forces LF line
endings (via ensureTrailingNewline) and loses CRLF round-trips; change
restoreConfigTomlFromAppBind to detect and preserve the original file's
lineEnding (reuse the same detection logic as rewriteConfigTomlForAppBind) and
ensureTrailingNewline respects that detected lineEnding instead of always using
"\n". Also wrap all raw writeFile calls invoked during bind/unbind (the places
that call writeFile from this module) with the existing withFileOperationRetry
helper (same behavior used in test/app-bind.test.ts) so EBUSY/file-lock errors
are retried; update tests to add a CRLF round-trip case (write with "\r\n" and
assert restore preserves "\r\n"), and add tests for idempotent re-bind,
provider-only-ending-without-trailing-newline, and restore when original lacked
top-level model_provider.
In `@package.json`:
- Line 104: The postinstall script currently only skips bind logic when
!isGlobalNpmInstall(env) or !rotationEnabled, but it should also detect CI
environments and npm_config_ignore_scripts; update scripts/postinstall.js to
short-circuit when CI is detected (check common CI envs like CI, GITHUB_ACTIONS,
GITLAB_CI, CIRCLECI, and process.env.npm_config_ignore_scripts) in the same
guard area where isGlobalNpmInstall(env) and rotationEnabled are evaluated
(refer to the existing checks around lines referenced by isGlobalNpmInstall and
rotationEnabled); then add a unit/integration test in
test/install-codex-auth.test.ts that sets CI=true and GITHUB_ACTIONS=true (and
toggles npm_config_ignore_scripts) to assert the postinstall path is a no-op
under CI.
In `@scripts/codex-app-launcher.js`:
- Around line 399-415: The code grabs the last non-empty stdout line into output
and calls JSON.parse(output) without context; wrap the parse in a try/catch
around the JSON.parse call (referencing the output variable and the place where
runCommand is awaited) and on failure throw a new Error that includes the
original parse error.message plus the raw output string so the caller (which
logs error.message) receives full diagnostic context.
- Around line 345-352: The current createMacLauncherScript uses JSON.stringify
to quote plan values which is unsafe for POSIX shells; modify
createMacLauncherScript to use the existing shell-quoter quoteDesktopExec for
all embedded values: quote the working directory, quote plan.commandPath, and
quote each entry of plan.commandArgs (map quoteDesktopExec over plan.commandArgs
and join with spaces) so the generated lines become cd <quoted-workingDirectory>
and exec <quoted-commandPath> <quoted-args>; keep the same array->join("\n")
output but replace JSON.stringify usages with quoteDesktopExec calls.
- Around line 79-81: The helper resolveWindowsDesktopDir in
scripts/codex-app-launcher.js currently hardcodes join(home, "Desktop"), which
fails for OneDrive-redirected or policy-redirected desktops; change resolution
to query the authoritative Windows shell folder instead (invoke PowerShell to
read [Environment]::GetFolderPath('Desktop') or run reg query
"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" /v
Desktop) and return that path from resolveWindowsDesktopDir so the launcher uses
the real desktop root; also add a regression test in
test/install-codex-auth.test.ts that stubs OneDrive/overrides home and asserts
the resolved roots include the redirected desktop path returned by the new
resolution logic.
- Around line 242-294: The backup routine is incorrectly capturing
already-managed shortcuts as "original" because it only checks
$BackupByPath.ContainsKey($Path) before adding backups; change the logic in the
routing loop that populates $BackupsToWrite/$BackupByPath (the block around
$BackupByPath.ContainsKey($Path) and the creation of $Backup) to first inspect
the shortcut sentinel (e.g. $Shortcut.Description and $Shortcut.TargetPath) and
only record the current shortcut as the original if it is NOT already managed by
our wrapper (i.e. Description != 'Launch Codex through codex-multi-auth runtime
rotation' and TargetPath != our known wrapper path); leave existing backups
untouched otherwise and only add new entries when the shortcut is unmanaged so
restores can recover real original targets.
In `@scripts/codex-app-router.js`:
- Around line 85-93: The host selection currently accepts stateRecord.host or
args.host without restricting to loopback, which can expose the proxy; change
the host validation in scripts/codex-app-router.js after computing host to
assert it is loopback-only (accept "localhost", "127.0.0.1", "::1" and their
bracketed/hostname forms) and fail startup (throw or process.exit with an error)
if not loopback; ensure the subsequent binding that uses the host variable (the
router/proxy bind code) will only receive a validated loopback host. Also add a
regression test in test/app-bind.test.ts that starts the router/initialization
with a non-loopback host (e.g., "0.0.0.0" or "192.168.0.1") and asserts startup
is rejected before binding when codexRuntimeRotationProxy is enabled.
In `@scripts/codex.js`:
- Around line 1214-1238: The current syncShadowHomeStateBack loop operates
per-file (SHADOW_HOME_STATE_FILES) causing cross-process interleaving; change it
to acquire a cross-process bundle lease/lock (e.g., a lease file in
shadowCodexHome) before comparing/writing any files so the whole bundle is
synced atomically. Implement: in syncShadowHomeStateBack acquire a lease/lock
file (with timeout and safe fail if not acquired), then read
originalShadowHomeState and captureShadowHomeState for all names, decide the
bundle update, write bundle updates to temp files and perform atomic renames to
originalCodexHome, call tightenShadowHomePermissions, and finally release the
lease; ensure syncShadowHomeStateFile is only used internally after the lease is
held or refactor its calls into the bundle update path, and keep best-effort
catch to not crash if lease operations fail.
---
Duplicate comments:
In `@docs/features.md`:
- Line 27: Update the features table row to indicate runtime rotation is opt-in
by adding a qualifier like "opt-in / disabled by default
(codexRuntimeRotationProxy = false)" and/or "requires `codex auth rotation
enable`" so it matches the default in lib/config.ts and the
docs/configuration.md; reference the feature name codexRuntimeRotationProxy and
the existing CLI hint `codex auth rotation enable` (the row currently "Runtime
rotation proxy") when editing docs/features.md to make the default-off behavior
explicit.
In `@docs/reference/commands.md`:
- Line 61: The table row contains unescaped pipe characters in the cell holding
the rotation subcommands (the string shown as codex auth rotation
enable|disable|status|bind-app|unbind-app) which breaks the Markdown table; edit
the commands.md table row to escape each pipe as \| so the entire command
remains a single cell. Locate the row displaying the rotation commands (ref:
rotation.ts command set in lib/codex-manager/commands/rotation.ts for the exact
subcommand list) and replace each '|' with '\|' in that table cell so the
Markdown renders correctly.
In `@scripts/codex.js`:
- Around line 1600-1639: The current createRuntimeRotationProxyContextIfEnabled
flow returns null on any proxy startup or shadow-home error which causes caller
hard-exit; change it to fall back to returning baseContext (not null) on
failures from loadRuntimeRotationProxyModule,
proxyModule.startRuntimeRotationProxy, createRuntimeRotationProxyCodexHome, or
proxyServer.close cleanup, and only return null (or propagate a fatal error) if
an explicit strict mode env/flag is set (implement a new boolean check for
CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY that accepts "0"/"1" and a separate
strict override like CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY_STRICT); keep
createRuntimeRotationAppHelperContext behavior for app commands, ensure
baseContext.cleanup is still called on best-effort cleanup, and add/update tests
in test/codex-bin-wrapper.test.ts to assert fallback-to-baseContext vs
strict-mode null/exit behavior and that opt-in via
CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=1 triggers proxy attempts.
- Around line 1119-1165: The TOML header regex in
removeRuntimeRotationProviderBlock and rewriteTopLevelModelProvider only matches
single-bracket tables and misses array-of-tables like [[...]], so update both
header checks (the /^\s*\[[^\]]+\]\s*$/ tests in
removeRuntimeRotationProviderBlock and the isTable test in
rewriteTopLevelModelProvider) to match one-or-two-bracket headers (e.g.
/^\s*(\[\[|\[)[^\]]+(\]\]|\])\s*$/) so injected model_provider goes at
top-level, and add a vitest regression in codex-bin-wrapper.test.ts that
includes a leading [[...]] block to verify the behavior.
In `@test/codex-manager-status-command.test.ts`:
- Around line 116-135: Add deterministic tests for the remaining restoreReason
branches ("empty-storage" and "missing-storage") in the same style as the
existing "intentional-reset" test: createStatusDeps with loadAccounts returning
restoreReason set to "empty-storage" and "missing-storage", call
runStatusCommand(deps), assert deps.logInfo was called with the corresponding
human message (matching the messages produced in
lib/codex-manager/commands/status.ts for those branches) and also assert the
command result/exit code equals 0; use the same helpers (createStatusDeps,
runStatusCommand, deps.logInfo) so the new tests cover all restoreReason paths
and include the result === 0 parity check.
In `@test/runtime-rotation-proxy.test.ts`:
- Around line 139-348: The test suite is missing deterministic regression tests
for the x-api-key auth path, concurrent-request account-state updates,
refresh-token race dedupe, and a strict pool-exhaustion retry_after_ms
assertion; add vitest cases that (1) exercise the x-api-key branch by sending a
/responses request with x-api-key instead of Bearer and assert the upstream call
contains x-api-key and is authorized via that branch (use
startRuntimeRotationProxy or startProxy and postResponses), (2) reproduce
concurrent requests against shared AccountManager by issuing parallel
postResponses calls (Promise.all) and assert account rotation and accountManager
state updates are consistent (reference AccountManager, getAccountByIndex and
proxy.getStatus), (3) reproduce two simultaneous expired-token requests that
trigger token refresh and ensure the refresh is deduped (simulate upstream
responses in createRecordingFetch that return 401/expired then successful token,
assert only one refresh attempt and both client responses succeed), and (4)
tighten the pool-exhaustion test to assert payload.error.retry_after_ms equals
the exact value computed by AccountManager/createStorage (use deterministic
createStorage(now, ...) to control timing) rather than just >0; ensure all tests
use vitest deterministically, the existing helpers (createRecordingFetch,
createStorage, startProxy/startRuntimeRotationProxy, postResponses) and never
depend on real secrets or nondeterministic timers.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 9b4dfcf7-8b3b-457a-a71f-c88959b2005e
📒 Files selected for processing (26)
README.mddocs/configuration.mddocs/features.mddocs/reference/commands.mddocs/reference/settings.mddocs/releases/v1.3.1.mdlib/codex-manager.tslib/codex-manager/commands/rotation.tslib/codex-manager/commands/status.tslib/runtime-rotation-proxy.tslib/runtime/app-bind.tslib/runtime/runtime-observability.tspackage.jsonscripts/codex-app-launcher.jsscripts/codex-app-router.jsscripts/codex.jsscripts/postinstall.jstest/app-bind.test.tstest/codex-bin-wrapper.test.tstest/codex-manager-rotation-command.test.tstest/codex-manager-status-command.test.tstest/documentation.test.tstest/install-codex-auth.test.tstest/package-bin.test.tstest/runtime-observability.test.tstest/runtime-rotation-proxy.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{json,js,ts}
📄 CodeRabbit inference engine (README.md)
Store OAuth account credentials in project-scoped directories under
~/.codex/multi-auth/projects/<project-key>/with central settings in~/.codex/multi-auth/settings.json
Files:
test/documentation.test.tstest/package-bin.test.tslib/runtime/runtime-observability.tspackage.jsontest/runtime-observability.test.tslib/codex-manager/commands/status.tsscripts/codex-app-router.jslib/codex-manager.tstest/codex-manager-status-command.test.tstest/install-codex-auth.test.tstest/codex-manager-rotation-command.test.tstest/app-bind.test.tslib/codex-manager/commands/rotation.tsscripts/postinstall.jstest/runtime-rotation-proxy.test.tsscripts/codex-app-launcher.jslib/runtime-rotation-proxy.tstest/codex-bin-wrapper.test.tslib/runtime/app-bind.tsscripts/codex.js
test/**
⚙️ CodeRabbit configuration file
tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior. reject changes that mock real secrets or skip assertions.
Files:
test/documentation.test.tstest/package-bin.test.tstest/runtime-observability.test.tstest/codex-manager-status-command.test.tstest/install-codex-auth.test.tstest/codex-manager-rotation-command.test.tstest/app-bind.test.tstest/runtime-rotation-proxy.test.tstest/codex-bin-wrapper.test.ts
docs/**
⚙️ CodeRabbit configuration file
keep README, SECURITY, and docs consistent with actual CLI flags and workflows. whenever behavior changes, require updated upgrade notes and mention new npm scripts.
Files:
docs/reference/settings.mddocs/features.mddocs/releases/v1.3.1.mddocs/reference/commands.mddocs/configuration.md
lib/**
⚙️ CodeRabbit configuration file
focus on auth rotation, windows filesystem IO, and concurrency. verify every change cites affected tests (vitest) and that new queues handle EBUSY/429 scenarios. check for logging that leaks tokens or emails.
Files:
lib/runtime/runtime-observability.tslib/codex-manager/commands/status.tslib/codex-manager.tslib/codex-manager/commands/rotation.tslib/runtime-rotation-proxy.tslib/runtime/app-bind.ts
**/commands/**/*.{ts,js}
📄 CodeRabbit inference engine (README.md)
Implement
codex authcommand toolkit with subcommands:login,status,check,list,switch,forecast,verify-flagged,verify,fix,doctor,why-selected, androtation status
Files:
lib/codex-manager/commands/status.tslib/codex-manager/commands/rotation.ts
**/commands/status.{ts,js}
📄 CodeRabbit inference engine (README.md)
Surface recent runtime request metrics in
codex auth statustext output and expose machine-readable cooldown/runtime snapshot incodex auth report --json
Files:
lib/codex-manager/commands/status.ts
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:58:23.228Z
Learning: Override configuration root with `CODEX_MULTI_AUTH_DIR` environment variable for custom storage paths
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:58:23.228Z
Learning: Disable whole-pool replay by default when all accounts are rate-limited to prevent infinite retry loops
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:58:23.228Z
Learning: Implement bounded outbound request budget per active request to prevent a single prompt from walking the full account pool indefinitely
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:58:23.228Z
Learning: Trigger short cooldown after repeated cross-account 5xx bursts instead of continuing aggressive account rotation
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:58:23.228Z
Learning: Stagger proactive token refresh to reduce background refresh bursts across the account pool
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:58:23.228Z
Learning: Validate configuration after changes by running `codex auth status`, `codex auth check`, and `codex auth forecast --live`
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:58:23.228Z
Learning: Implement non-destructive experimental settings flows with preview-before-apply sync, destination-only account preservation, and fail-safe backup filename collision handling
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:58:23.228Z
Learning: Keep Responses background mode as opt-in only; enable `backgroundResponses` in settings or via `CODEX_AUTH_BACKGROUND_RESPONSES=1` only for callers that intentionally send `background: true`
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:58:37.059Z
Learning: Use `CODEX_MULTI_AUTH_CONFIG_PATH` environment variable to load configuration from an alternate path when unified settings are absent or invalid
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:58:37.059Z
Learning: Use `CODEX_MULTI_AUTH_DIR` environment variable to override the root directory for plugin-managed runtime files
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:58:37.059Z
Learning: Implement configuration resolution precedence: unified settings `pluginConfig` from `settings.json` first, fallback to `CODEX_MULTI_AUTH_CONFIG_PATH`, then hardcoded defaults
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:58:37.059Z
Learning: Environment variables override individual runtime settings after config source selection
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:58:37.059Z
Learning: Resolve dashboard display values from persisted `dashboardDisplaySettings` and then normalize defaults
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:58:37.059Z
Learning: Support `CODEX_MODE=0/1` to disable or enable Codex mode
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:58:37.059Z
Learning: Support `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=0/1` to opt in to live Codex Responses routing through the localhost account-rotation proxy
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:58:37.059Z
Learning: Support `CODEX_TUI_V2=0/1` to disable or enable TUI v2
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:58:37.059Z
Learning: Support `CODEX_TUI_COLOR_PROFILE` environment variable to select color profile (truecolor, ansi256, or ansi16)
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:58:37.059Z
Learning: Support `CODEX_TUI_GLYPHS` environment variable to select glyph mode (ascii, unicode, or auto)
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:58:37.059Z
Learning: Support `CODEX_AUTH_FETCH_TIMEOUT_MS` environment variable to override HTTP request timeout
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:58:37.059Z
Learning: Support `CODEX_AUTH_STREAM_STALL_TIMEOUT_MS` environment variable to override stream stall timeout
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:58:37.059Z
Learning: When `codexRuntimeRotationProxy` is enabled, the wrapper starts a localhost-only Responses proxy for forwarded official Codex sessions and replaces outbound auth headers with the selected managed account
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:58:37.059Z
Learning: `codex auth rotation enable` binds the packaged desktop app to a persistent localhost router by backing up the real Codex `config.toml`, writing the `codex-multi-auth-runtime-proxy` provider, starting the router, and installing a startup entry
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:58:37.059Z
Learning: `codex auth rotation disable` and `codex auth rotation unbind-app` stop the router, remove the startup entry, and restore the backed-up Codex config
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:58:37.059Z
Learning: Set `CODEX_MULTI_AUTH_APP_BIND_INSTALL=0` to skip install/update self-heal, or `CODEX_MULTI_AUTH_APP_BIND_INSTALL=1` to force it
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:58:37.059Z
Learning: Set `CODEX_MULTI_AUTH_APP_LAUNCHER_INSTALL=0` before enabling rotation to skip shortcut routing, or run `codex-multi-auth-app-launcher --remove` to restore backed-up Windows shortcuts or remove the managed macOS wrapper
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:58:37.059Z
Learning: The wrapper and plugin should try GPT model aliases directly and only fall back to `gpt-5.4` after a real ChatGPT Codex unsupported-model response
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:01.564Z
Learning: After enabling `backgroundResponses`, test one known `background: true` request end-to-end before rolling across shared automation
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:01.564Z
Learning: Use environment variable `CODEX_MULTI_AUTH_DIR` to override the custom root directory for settings, accounts, cache, and logs
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:01.564Z
Learning: Use environment variable `CODEX_MULTI_AUTH_CONFIG_PATH` to specify an alternate config file input
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:01.564Z
Learning: Use environment variable `CODEX_MODE` to toggle Codex mode
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:01.564Z
Learning: Use environment variable `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY` to toggle opt-in localhost Responses proxy for forwarded Codex sessions
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:01.564Z
Learning: Use environment variable `CODEX_TUI_V2` to toggle TUI v2
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:01.564Z
Learning: Use environment variable `CODEX_TUI_COLOR_PROFILE` to set TUI color profile
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:01.564Z
Learning: Use environment variable `CODEX_TUI_GLYPHS` to set TUI glyph mode
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:01.564Z
Learning: Use environment variable `CODEX_AUTH_FETCH_TIMEOUT_MS` to override request timeout
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:01.564Z
Learning: Use environment variable `CODEX_AUTH_STREAM_STALL_TIMEOUT_MS` to override stream stall timeout
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:01.564Z
Learning: Use environment variable `CODEX_MULTI_AUTH_SYNC_CODEX_CLI` to toggle Codex CLI state sync
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:01.564Z
Learning: Use environment variable `CODEX_MULTI_AUTH_REAL_CODEX_BIN` to force official Codex binary path
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:01.564Z
Learning: Use environment variable `CODEX_MULTI_AUTH_BYPASS` to bypass local auth handling
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:01.564Z
Learning: Use temp-file + rename semantics for storage writes to handle transient `EPERM`/`EBUSY` errors on Windows
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:01.564Z
Learning: Use lease/state files for cross-process refresh coordination; avoid manually editing those files while the CLI is running
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:01.564Z
Learning: Combine `fs.watch` with polling fallback to handle Windows file watcher edge cases for live account sync
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:01.564Z
Learning: Allow backup/WAL artifacts to exist briefly during storage writes and recovery as part of normal safety behavior
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:16.545Z
Learning: Store account data in canonical local data root at `~/.codex/multi-auth`
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:16.545Z
Learning: Implement project-scoped account pools for repo-specific account state separation
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:16.545Z
Learning: Support linked-worktree identity sharing to allow the same repository to share account state across worktrees
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:16.545Z
Learning: Persist quota cache between runs for faster forecast and dashboard visibility
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:16.545Z
Learning: Implement multi-account dashboard login via `codex auth login` command to add and manage multiple OAuth identities
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:16.545Z
Learning: Implement onboarding backup restore to restore latest named backup or allow manual selection when recovering saved accounts
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:16.545Z
Learning: Implement account dedupe and identity normalization in login flow to avoid duplicate saved account rows
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:16.545Z
Learning: Implement explicit active-account switching via `codex auth switch <index>` command to pick current account by index
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:16.545Z
Learning: Implement fast and deep health checks via `codex auth check` command to verify pool usability
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:16.545Z
Learning: Implement flagged-account verification and restore via `codex auth verify-flagged` to recover sidelined accounts
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:16.545Z
Learning: Implement readiness and risk forecast via `codex auth forecast` command to suggest the best next account
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:16.545Z
Learning: Support live quota probe mode via `codex auth forecast --live` using live headers for stronger decisions
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:16.545Z
Learning: Implement JSON report output via `codex auth report --live --json` for automation and support workflows
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:16.545Z
Learning: Implement runtime rotation proxy via `codex auth rotation enable` to allow managed accounts rotation between requests without session restart
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:16.545Z
Learning: Implement safe repair workflow via `codex auth fix` to detect and repair local storage inconsistencies
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:16.545Z
Learning: Implement diagnostics with optional repair via `codex auth doctor` command to inspect and fix common failures
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:16.545Z
Learning: Implement backup and WAL recovery at storage runtime for safer persistence during interrupted or partially applied local writes
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:16.545Z
Learning: Keep OAuth login browser-first by default, while supporting `--manual`, `--no-browser`, and `CODEX_AUTH_NO_BROWSER=1` fallback options for browser-restricted shells
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:16.545Z
Learning: Support manual/non-TTY login by accepting the full callback URL on stdin for automation and host-managed shells
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:16.545Z
Learning: Implement quick switch and search hotkeys in dashboard for faster navigation
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:16.545Z
Learning: Implement account action hotkeys in dashboard for per-account set, refresh, toggle, and delete shortcuts
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:16.545Z
Learning: Implement in-dashboard settings hub for runtime and display tuning without editing files directly
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:16.545Z
Learning: Implement experimental settings hotkeys in dashboard for sync preview, backup export, and refresh-guard tuning
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:16.545Z
Learning: When implementing plugin-host runtime, use the same account pool for request transformation, token refresh, retry handling, session affinity, and capability-aware account selection
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:35.344Z
Learning: The codex-multi-auth documentation structure should include references to: features.md, public-api.md, error-contracts.md, settings.md, and troubleshooting.md
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:54.636Z
Learning: Configuration settings file location should default to `~/.codex/multi-auth/settings.json` unless `CODEX_MULTI_AUTH_DIR` environment variable is set
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:54.636Z
Learning: Recommended defaults for most environments include: smart sort enabled, auto-fetch limits enabled, live sync enabled, session affinity enabled, preemptive quota deferral enabled, and proactive refresh guardian enabled
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:54.636Z
Learning: After configuration changes, validation should be performed using: `codex auth status`, `codex auth check`, and `codex auth forecast --live` commands
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T14:59:54.636Z
Learning: Environment variable overrides for stable configurations should include: CODEX_MULTI_AUTH_DIR, CODEX_MULTI_AUTH_CONFIG_PATH, CODEX_MODE, CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY, CODEX_MULTI_AUTH_APP_BIND_INSTALL, CODEX_TUI_V2, CODEX_TUI_COLOR_PROFILE, CODEX_TUI_GLYPHS, CODEX_AUTH_FETCH_TIMEOUT_MS, CODEX_AUTH_STREAM_STALL_TIMEOUT_MS
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T15:00:02.010Z
Learning: Preserve deterministic fallback behavior for older official Codex runtimes and non-entitled or quota-limited accounts
🔇 Additional comments (4)
lib/runtime-rotation-proxy.ts (4)
139-141: still a dead branch.
lib/runtime-rotation-proxy.ts:136already setsACCOUNT_IDtoaccountId, andaccountIdis the exact value re-written here. the guard is a strict no-op and will bait a future reader into mis-editing it.
145-151: constant-time compare still missing.
lib/runtime-rotation-proxy.ts:149-150still uses===on the bearer andx-api-key. loopback + 32-byte secret keeps the attack surface small, but any local process (ci agents, sidecar extensions, snoopy dev tools) can probe.crypto.timingSafeEqualwith length guard is trivial.
217-223: still no body-size cap.
lib/runtime-rotation-proxy.ts:219-221drains every chunk into memory with no ceiling; any loopback caller (including the defaultclientApiKey === nullbranch) can exhaust heap. same 64 MiB cap + 413 response still applies, plus the missing regression test intest/runtime-rotation-proxy.test.ts.
766-795: 401 still does not refund; 5xx does.
lib/runtime-rotation-proxy.ts:766-778(401) skipsrefundToken,lib/runtime-rotation-proxy.ts:781-795(5xx) calls it, andlib/runtime-rotation-proxy.ts:745-764(429) also skips intentionally. pick one policy and drop a single-line comment in each branch so the next person doesn't "fix" the divergence in the wrong direction. if the intent is "401 never charged upstream quota, so refund", mirror the 5xx branch.
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
scripts/codex.js (1)
537-601:⚠️ Potential issue | 🔴 Criticalno error listeners on stdin/stdout/stderr streams in protocol-proxy path — unhandled stream errors crash the wrapper.
scripts/codex.js:548 calls
child.stdin.write(chunk)with only adestroyedguard, but node streams emiterrorevents for write failures (epipe when child exits, econnreset on unusual builds). there's adrainlistener at line 586 handling backpressure, but noerrorlistener on stdin, stdout, or stderr. child.once("error") at line 603 catches spawn errors only, not stream errors. an unhandled error on any of these streams becomes uncaughtException and kills the wrapper mid-session.same applies to child.stdout/stderr — both lack error listeners. for a feature designed to keep rotation alive across long
codex app-serversessions, this is a serious reliability gap, especially on windows where pipe teardown races are common.add
child.stdin?.on("error", onChildStdinError), mirror for stdout/stderr (log via console.error, close proxy), register in cleanupProtocolProxy closure, and add vitest regression that kills the mocked child mid-stream and asserts the wrapper exits cleanly without throwing.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/codex.js` around lines 537 - 601, The protocol-proxy path is missing stream "error" handlers which can crash the wrapper; add error listeners for child.stdin, child.stdout, and child.stderr (e.g., onChildStdinError, onChildStdoutError, onChildStderrError) that log the error (console.error) and call closeChildStdin()/protocolProxy.flushOutput()/cleanupProtocolProxy() as appropriate, register these listeners where process.stdin.on/... and child.stdout/on(...) are currently attached, and remove them inside cleanupProtocolProxy so they don't leak; also add a vitest regression that simulates killing the child mid-stream and asserts the wrapper exits cleanly (no uncaught exceptions) to cover the new handlers.
♻️ Duplicate comments (6)
test/codex-bin-wrapper.test.ts (1)
169-226:⚠️ Potential issue | 🟠 Majorfixture still drifts from
RuntimeRotationProxyStatusand negative-path is uncovered.
test/codex-bin-wrapper.test.ts:196-209builds a status missingstartedAtandstreamsStarted, both declared onRuntimeRotationProxyStatusatlib/runtime-rotation-proxy.ts:31-53. today onlytotalRequestsis read inscripts/codex.js:1714, but the app-helper tests at:921-927already lean on other fields; as the shim grows to readstreamsStartedthe fixture will returnundefinedand silently pass."function buildStatus() {", " return {", + " startedAt: readOptionalNumberEnv('CODEX_MULTI_AUTH_TEST_PROXY_STARTED_AT') ?? 0,", " totalRequests: readOptionalNumberEnv('CODEX_MULTI_AUTH_TEST_PROXY_REQUESTS') ?? 0,", " upstreamRequests: 0,", " retries: 0,", " rotations: readOptionalNumberEnv('CODEX_MULTI_AUTH_TEST_PROXY_ROTATIONS') ?? 0,", + " streamsStarted: readOptionalNumberEnv('CODEX_MULTI_AUTH_TEST_PROXY_STREAMS_STARTED') ?? 0,",separately: there is still no wrapper-surface test where
startRuntimeRotationProxyrejects (module missing or throws).scripts/codex.js:1700-1722has a fallback path ("runtime rotation proxy module is unavailable"); without a negative test you cannot catch a regression wherecodex execdies instead of degrading. add a fixture variant that throws insidestartRuntimeRotationProxy(or skips writingdist/lib/runtime-rotation-proxy.js) and assert exit status + non-proxy forwarding.as per coding guidelines for
test/**: "demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior" — the proxy-start failure path is a concurrency-adjacent reliability gap worth locking in.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/codex-bin-wrapper.test.ts` around lines 169 - 226, The test fixture created by createRuntimeRotationProxyFixtureModule returns a status missing startedAt and streamsStarted (fields defined on RuntimeRotationProxyStatus) and lacks a negative test for startRuntimeRotationProxy failure; update the fixture module written in test/codex-bin-wrapper.test.ts so buildStatus() includes startedAt and streamsStarted (populate from CODEX_MULTI_AUTH_TEST_PROXY_* env vars or sensible defaults) to match RuntimeRotationProxyStatus, and add a new test variant that simulates the module failing to start (either by writing a module whose startRuntimeRotationProxy throws or by omitting dist/lib/runtime-rotation-proxy.js) and assert codex exec falls back to the "runtime rotation proxy module is unavailable" path (check exit status and non-proxy forwarding behavior exercised in scripts/codex.js handling around startRuntimeRotationProxy).test/runtime-rotation-proxy.test.ts (1)
139-380:⚠️ Potential issue | 🔴 Criticalstill missing concurrency, token-refresh-race, and x-api-key regression coverage.
re-reading the file end-to-end against the past review: none of the four gaps are closed.
- x-api-key auth unreachable in tests.
lib/runtime-rotation-proxy.ts:148-154has a distinctheaders.get("x-api-key") === clientApiKeybranch. the only auth test attest/runtime-rotation-proxy.test.ts:139-174sends Bearer at line 168; the x-api-key branch is untested.- no concurrent inbound requests.
Promise.alldoes not appear in the suite.lib/runtime-rotation-proxy.tsruns the handler concurrently and mutates shared status counters + callsconsumeToken/refundToken/markRateLimitedWithReasonin parallel. fire N requests simultaneously, assert counters and account picks stay consistent (no negative budgets, no duplicate picks).- no token-refresh dedup test. every account uses
expiresAt: now + 3_600_000attest/runtime-rotation-proxy.test.ts:46, soensureFreshAccessToken→queuedRefreshdedup contract never fires. setexpiresAt < now()and fire two concurrent requests; assertsaveAccountsMockis called exactly once and both responses succeed.- pool-exhaustion assertion still loose.
:350only checksretry_after_ms > 0. pin it toaccountManager.getMinWaitTimeForFamily(family, model)so regressions inwritePoolExhaustedsurface.per coding guidelines for
test/**: "demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior." the first two categories are still uncovered on the proxy's hottest paths.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/runtime-rotation-proxy.test.ts` around lines 139 - 380, Add tests exercising the missing branches and races: (1) add a case that authenticates using x-api-key (send header "x-api-key" equal to clientApiKey) to hit the headers.get("x-api-key") === clientApiKey branch when calling startRuntimeRotationProxy/postResponses; (2) create a concurrency test that fires many simultaneous postResponses via Promise.all against startProxy/startRuntimeRotationProxy and assert AccountManager state (no negative budgets, unique account picks, and proxy.getStatus() counters) and that consumeToken/refundToken paths behave deterministically; (3) add a token-refresh-race test by using createStorage or AccountManager to set an account's expiresAt < now and then issue two concurrent requests, asserting ensureFreshAccessToken/queuedRefresh dedup behavior by checking the accounts persistence mock (saveAccountsMock) was called exactly once and both requests succeed; (4) tighten the pool-exhaustion assertion by comparing the returned payload.error.retry_after_ms to accountManager.getMinWaitTimeForFamily(family, model) (rather than just > 0) so writePoolExhausted regressions are caught.scripts/codex.js (3)
1316-1362:⚠️ Potential issue | 🔴 Criticalarray-of-tables table regex is still broken —
model_providerends up nested under the last[[…]]block.
scripts/codex.js:1326(removeRuntimeRotationProviderBlock) andscripts/codex.js:1344(rewriteTopLevelModelProvider) both still use/^\s*\[[^\]]+\]\s*$/, which does not match[[profiles.foo]]or any other TOML array-of-tables header. if a user'sconfig.tomlopens with an array-of-tables (common in custom profile configs),rewriteTopLevelModelProviderskips the header detection, falls through to the trailing append branch at line 1357-1359, and silently nestsmodel_provider = "codex-multi-auth-runtime-proxy"under the last[[…]]block. codex ignores it and the proxy never activates — rotation looks enabled incodex auth rotation statusbut traffic still hits the real provider.fix both regexes to
/^\s*\[\[?[^\]]+\]\]?\s*$/and land a regression fixture with a leading[[profiles.foo]]header againstrewriteConfigTomlForRuntimeRotationProxy. as per coding guidelines (lib/**: "focus on auth rotation …"), this needs a cited vitest regression before merge.🛠 proposed fix
- if (skipping && /^\s*\[[^\]]+\]\s*$/.test(line)) { + if (skipping && /^\s*\[\[?[^\]]+\]\]?\s*$/.test(line)) { skipping = false; }- const isTable = /^\s*\[[^\]]+\]\s*$/.test(line); + const isTable = /^\s*\[\[?[^\]]+\]\]?\s*$/.test(line);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/codex.js` around lines 1316 - 1362, The TOML table regex in removeRuntimeRotationProviderBlock and rewriteTopLevelModelProvider is too narrow and misses array-of-tables headers like [[profiles.foo]]; update the regex in both functions (removeRuntimeRotationProviderBlock, rewriteTopLevelModelProvider) from /^\s*\[[^\]]+\]\s*$/ to /^\s*\[\[?[^\]]+\]\]?\s*$/ so table headers and array-of-tables are detected properly, and add a vitest regression test for rewriteConfigTomlForRuntimeRotationProxy that uses a config starting with a leading [[profiles.foo]] header to assert model_provider gets placed at top level (not nested).
636-642:⚠️ Potential issue | 🔴 Criticalproxy unavailability still hard-exits
1despite being marked addressed.the past "✅ Addressed in commit c2c1ec6" marker is misleading —
scripts/codex.js:640-642still early-returns exit1whencreateRuntimeRotationProxyContextIfEnabledyields null, andscripts/codex.js:1900-1904+scripts/codex.js:1916-1927still produce null on dist-missing / port contention / startup throws. there is noCODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY_STRICTopt-in, no fallback tobaseContext, and no vitest regression that stubsstartRuntimeRotationProxyto throw and asserts the wrapper still forwards to the real codex.the operational consequence is unchanged: a partial
npm run build, transient fs hiccup, or port collision flips rotation from an opt-in perf feature into "codex refuses to run." please either actually implement the fallback-by-default behavior requested earlier, or update the resolution marker and document the strict-by-design contract indocs/reference/commands.md:154so operators know to expectcodex <anything>to die when rotation is enabled and dist is half-built.Also applies to: 1887-1951
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/codex.js` around lines 636 - 642, When createRuntimeRotationProxyContextIfEnabled(...) returns null the caller currently returns exit code 1; change this to fall back to the original baseContext unless a new opt-in strict mode is set (env var CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY_STRICT). Specifically, update the caller around runtimeProxyContext to: if runtimeProxyContext is null and CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY_STRICT is truthy, log an error and exit 1; otherwise log a warning and continue using baseContext; alternatively move this logic inside createRuntimeRotationProxyContextIfEnabled or have startRuntimeRotationProxy surface errors rather than causing null silently—ensure all callers (including the one invoking startRuntimeRotationProxy) respect the same strict opt-in and that tests are added to simulate startRuntimeRotationProxy throwing and assert the wrapper falls back to baseContext.
1416-1441:⚠️ Potential issue | 🔴 Criticalshadow-home sync-back still per-file — two wrapper sessions can publish a mixed auth bundle.
scripts/codex.js:1416-1441still iteratesSHADOW_HOME_STATE_FILESand callssyncShadowHomeStateFileindependently per name. two overlapping sessions (very plausible forcodex app+codex execfrom the same operator) can interleave renames, leaving the realCODEX_HOMEwithauth.jsonfrom one session andaccounts.json/.codex-global-state.jsonfrom another. the PR summary itself calls this out under "Concurrent shadow-home sync race in scripts/codex.js affecting concurrent codex app sessions."per-file snapshot-equality checks don't close the window — they only reject the rename when the destination drifted; they don't make the three writes atomic as a bundle. acquire a cross-process lease file inside
originalCodexHome(or a stablemulti-authdir) before the loop, capture + write all three in the held lease, then release. regression test must spin up two concurrentsyncShadowHomeStateBackinvocations against shared tmp dirs and assert the final bundle matches one source, not a mix.based on learnings: "Cross-process refresh coordination relies on lease/state files; avoid manually editing those files while the CLI is running to prevent synchronization conflicts" — apply the same pattern here.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/codex.js` around lines 1416 - 1441, The per-file sync in syncShadowHomeStateBack allows interleaved sessions to produce mixed auth files; fix by acquiring a cross-process lease (e.g., a lock file inside originalCodexHome or a stable multi-auth dir) before iterating SHADOW_HOME_STATE_FILES, then while holding the lease capture the current snapshots, validate originalSnapshot equality, and write all three files as an atomic bundle (replace per-file calls to syncShadowHomeStateFile with a single bundled write + tightenShadowHomePermissions on the bundle), release the lease after completion; add a regression test that spawns two concurrent syncShadowHomeStateBack calls against shared temp dirs and asserts the final CODEX_HOME bundle is entirely from one source (no mixed files).lib/codex-manager/commands/rotation.ts (1)
154-170:⚠️ Potential issue | 🟠 Major
isProcessAlive(pid)still lies on windows when the helper pid gets recycled.
lib/codex-manager/commands/rotation.ts:157-160still gates liveness onprocess.kill(pid, 0)plus the state string. on windows, pid reuse is routine — once the helper exits, a reused pid against a staleruntime-rotation-app-helper.json(state="running", oldupdatedAt) keeps printingCodex app helper: running, pid=…during an incident. operators runningcodex auth rotation statusmid-incident get the exact wrong answer.gate on status freshness: reject the status when
status.updatedAtis older than, say,max(2 * pollInterval, 5_000)ms, and/or whenstatus.idleExpiresAtis in the past. the helper already updates atsetInterval(…, min(1000, idle/2))inscripts/codex.js:1725-1735, so a 5s staleness threshold is safe. stronger fix: persist an instance token (random bytes or boot id) in the status file and write the live process's token into a pid-adjacent file so you can verify the pid belongs to this helper.regression test must write a
runtime-rotation-app-helper.jsonwith a staleupdatedAtand the currentprocess.pid(the vitest runner), then assertformatAppRuntimeHelperStatusreturns"Codex app helper: not running". as per coding guidelines (lib/**: "focus on auth rotation, windows filesystem IO, and concurrency"), this regression is required.🪟 freshness gate sketch
- const alive = isProcessAlive(status.pid); - if (!alive || status.state === "stopped" || status.state === "idle-timeout") { + const stalenessMs = 5_000; + const isFresh = status.updatedAt !== null && now - status.updatedAt <= stalenessMs; + const alive = isFresh && isProcessAlive(status.pid); + if (!alive || status.state === "stopped" || status.state === "idle-timeout") { return "Codex app helper: not running"; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/codex-manager/commands/rotation.ts` around lines 154 - 170, formatAppRuntimeHelperStatus currently trusts isProcessAlive(status.pid) and state but can misreport on Windows due to PID reuse; change it to reject a status if status.updatedAt is older than a freshnessThreshold (use max(2 * pollInterval, 5000) ms, or at least 5000ms when pollInterval isn't accessible) or if status.idleExpiresAt is in the past before reporting "running"; update formatAppRuntimeHelperStatus (and readAppRuntimeHelperStatus usage) to check status.updatedAt and status.idleExpiresAt first, then fall back to isProcessAlive(status.pid) and state; add a regression test that writes a runtime-rotation-app-helper.json with a stale updatedAt but the current process.pid (the test runner) and assert formatAppRuntimeHelperStatus returns "Codex app helper: not running".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/codex-manager/help.ts`:
- Around line 23-26: The help text currently shows "codex auth rotation
<enable|disable|status>" but omits the runtime proxy management subcommands;
update the help entry that contains the literal string "codex auth rotation
<enable|disable|status>" in help.ts to list all supported subcommands — enable,
disable, status, bind-app, and unbind-app — so the displayed line becomes "codex
auth rotation <enable|disable|status|bind-app|unbind-app>" (or equivalent
human-readable form) and ensure any surrounding documentation strings in the
same file reflect those five options.
In `@lib/logger.ts`:
- Line 49: Add a regression test to the "sanitizeValue edge cases" suite in
test/logger.test.ts that asserts sanitizeValue masks keys related to
experimental bearer tokens: include a case for the snake_case key
"experimental_bearer_token" and additional cases for normalized variations
"experimentalBearerToken" and "experimental-bearer-token"; ensure the assertions
verify the returned value is masked (not the raw token) and reference the
sanitizeValue function from lib/logger.ts (which recently added
"experimentalbearertoken" to the sensitive keys) and the source usage in
lib/runtime/app-bind.ts so future changes won't reintroduce leaks.
In `@lib/runtime-rotation-proxy.ts`:
- Line 49: The status object currently persists raw email via identity.email
into lastAccountEmail and runtime-observability state; change the code in
getStatus()/status construction (where identity.email is written, and the
runtime-observability mutation) to remove or redact the email (e.g., delete
lastAccountEmail or replace with a deterministic hash/placeholder) so no raw
email is stored or returned by getStatus(); keep index, label, accountId intact
and, if an email is needed for on-demand display, resolve it at read-time from
the in-memory account manager instead of persisting. Update tests: add a
regression in test/runtime-rotation-proxy.test.ts asserting getStatus() does not
include raw email (no '@') and add a test in test/app-bind.test.ts that reads
the persisted status file and asserts the snapshot contains no '@' characters or
raw email entries. Ensure you update references around symbols:
lastAccountEmail, getStatus(), identity.email, and runtime-observability.
In `@lib/runtime/app-bind.ts`:
- Around line 272-286: Wrap every raw fs write in the bind/unbind hot paths to
use the existing withFileOperationRetry helper: replace direct writeFile calls
that create the startup artifacts, plist/.cmd startup scripts, backup, state
file, config.toml rewrite and restore-path writes in lib/runtime/app-bind.ts
with await withFileOperationRetry(() => writeFile(...)) so transient
EBUSY/EPERM/EACCES are retried; update both bindCodexAppRuntimeRotation and
unbindCodexAppRuntimeRotation code paths to call withFileOperationRetry for the
writes that currently run raw (startup artifact writes, backup, state/config
writes, restore writes and fallback restore). Add Vitest cases in
test/app-bind.test.ts that stub writeFile to throw { code: "EBUSY" } for the
first N-1 attempts and assert bindCodexAppRuntimeRotation and
unbindCodexAppRuntimeRotation succeed, and a case where EBUSY persists past
FILE_RETRY_MAX_ATTEMPTS to assert the error is propagated.
- Around line 654-694: The code writes a config/state with baseUrl using port 0
when maybeStartRouter returns false; fix by resolving a valid non-zero port
before writing boundConfig/state: after calling maybeStartRouter(state, options)
if startedRouter is false, call waitForRouterStatus(state.statusPath) (or read
existingState/status) and use readPortFromBaseUrl(router?.baseUrl ?? null, port)
to update port and baseUrl (falling back to formatBaseUrl only if port is
non-zero), recompute boundConfig = rewriteConfigTomlForAppBind(content, baseUrl,
clientApiKey) and update state.boundConfigHash/port/baseUrl/updatedAt before
writing paths.configPath and paths.statePath; also add a test in
test/app-bind.test.ts to cover spawnDetached: false ensuring bind refuses or
uses existing state's port.
In `@scripts/codex-app-router.js`:
- Around line 110-117: The current port validation accepts non-integers and
out-of-range values (e.g., 3.14 or 70000); update the check around the computed
port variable to require an integer in the valid TCP port range: use
Number.isInteger(port) && port >= 0 && port <= 65535 (instead of
Number.isFinite/port < 0) and throw a clear error (referencing port, stateRecord
and args.port) so tampered state files are rejected before calling
startRuntimeRotationProxy/server.listen.
- Around line 141-157: The cleanup handler can be invoked multiple times by
rapid signals; make it idempotent by adding a single-shot guard (e.g., a boolean
flag like isCleaning) checked at the top of cleanup and set immediately to true
so subsequent invocations return early; inside cleanup still
clearInterval(timer), await proxyServer?.close() but only if not already closed
or if the guard indicates first run (catch and swallow or log close errors to
avoid unhandled rejection), and always call writeCurrentStatus(state) in the
finally; keep process.once registrations but rely on the single-shot guard to
prevent double-close and racing writeCurrentStatus calls.
In `@test/app-bind.test.ts`:
- Around line 231-254: The test currently passes a hardcoded relative script
path "scripts/codex-app-router.js" to spawnSync which breaks when the cwd is not
the repo root; update the spawnSync call to resolve the script path relative to
the test file (e.g. derive an absolute path from the test module directory using
path.resolve or fileURLToPath(import.meta.url) and use that instead of the raw
string) so spawnSync can always find the script, and adjust the assertions on
the spawn result: replace expect(result.status).toBe(1) with
expect(result.status).not.toBe(0) and add expect(result.error).toBeUndefined()
to ensure the process actually executed rather than spawn failing; keep
references to spawnSync, process.execPath, createTempRoot, statusPath,
result.status and result.error when making these changes.
In `@test/codex-manager-rotation-command.test.ts`:
- Around line 159-243: Add four deterministic vitest regression cases to
test/codex-manager-rotation-command.test.ts: (1) a concurrent-run test that
calls runRotationCommand(["enable"]) twice in overlapping async operations and
asserts savePluginConfigMock and bindCodexAppMock were called in a consistent
order and expected counts; (2) a stale-helper-status test that writes a
runtime-rotation-app-helper.json fixture with state:"running" and a reused pid
(simulate a non-existent process) then runs runRotationCommand(["status"]) and
expects the status output to include "Codex app helper: not running"; (3) a
Windows-EBUSY regression that simulates EBUSY/429 from savePluginConfigMock or
bindCodexAppMock (mock them to throw EBUSY-like errors on first attempt and
succeed on retry) and asserts the command recovers and saves/binds as expected;
and (4) an invalid-env-value test that sets
process.env.CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY="whatever", runs
runRotationCommand(["status"]) and asserts the output contains "Env override:
invalid (whatever)"; use existing helpers (createDeps, createStorage,
setStoragePathMock, infos, errors, savePluginConfigMock, bindCodexAppMock,
unbindCodexAppMock) and runRotationCommand to locate insertion points and keep
tests deterministic by seeding time and storage where needed.
In `@vitest.config.ts`:
- Around line 16-26: The shebang stripper currently looks for any "/scripts/"
segment in module ids (plugin name 'strip-script-shebangs-for-vitest',
transform) which can match dependencies; change the check to only match files
under this repo's scripts directory by computing the repo scripts root (e.g.,
path.join(process.cwd(), 'scripts') or process.cwd() + path.sep + 'scripts') and
require id.startsWith(thatRoot + path.sep) (or otherwise ensure the id is inside
the repo root scripts folder) before stripping the shebang; keep the other
checks (code.startsWith('#!')) intact.
---
Outside diff comments:
In `@scripts/codex.js`:
- Around line 537-601: The protocol-proxy path is missing stream "error"
handlers which can crash the wrapper; add error listeners for child.stdin,
child.stdout, and child.stderr (e.g., onChildStdinError, onChildStdoutError,
onChildStderrError) that log the error (console.error) and call
closeChildStdin()/protocolProxy.flushOutput()/cleanupProtocolProxy() as
appropriate, register these listeners where process.stdin.on/... and
child.stdout/on(...) are currently attached, and remove them inside
cleanupProtocolProxy so they don't leak; also add a vitest regression that
simulates killing the child mid-stream and asserts the wrapper exits cleanly (no
uncaught exceptions) to cover the new handlers.
---
Duplicate comments:
In `@lib/codex-manager/commands/rotation.ts`:
- Around line 154-170: formatAppRuntimeHelperStatus currently trusts
isProcessAlive(status.pid) and state but can misreport on Windows due to PID
reuse; change it to reject a status if status.updatedAt is older than a
freshnessThreshold (use max(2 * pollInterval, 5000) ms, or at least 5000ms when
pollInterval isn't accessible) or if status.idleExpiresAt is in the past before
reporting "running"; update formatAppRuntimeHelperStatus (and
readAppRuntimeHelperStatus usage) to check status.updatedAt and
status.idleExpiresAt first, then fall back to isProcessAlive(status.pid) and
state; add a regression test that writes a runtime-rotation-app-helper.json with
a stale updatedAt but the current process.pid (the test runner) and assert
formatAppRuntimeHelperStatus returns "Codex app helper: not running".
In `@scripts/codex.js`:
- Around line 1316-1362: The TOML table regex in
removeRuntimeRotationProviderBlock and rewriteTopLevelModelProvider is too
narrow and misses array-of-tables headers like [[profiles.foo]]; update the
regex in both functions (removeRuntimeRotationProviderBlock,
rewriteTopLevelModelProvider) from /^\s*\[[^\]]+\]\s*$/ to
/^\s*\[\[?[^\]]+\]\]?\s*$/ so table headers and array-of-tables are detected
properly, and add a vitest regression test for
rewriteConfigTomlForRuntimeRotationProxy that uses a config starting with a
leading [[profiles.foo]] header to assert model_provider gets placed at top
level (not nested).
- Around line 636-642: When createRuntimeRotationProxyContextIfEnabled(...)
returns null the caller currently returns exit code 1; change this to fall back
to the original baseContext unless a new opt-in strict mode is set (env var
CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY_STRICT). Specifically, update the caller
around runtimeProxyContext to: if runtimeProxyContext is null and
CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY_STRICT is truthy, log an error and exit
1; otherwise log a warning and continue using baseContext; alternatively move
this logic inside createRuntimeRotationProxyContextIfEnabled or have
startRuntimeRotationProxy surface errors rather than causing null
silently—ensure all callers (including the one invoking
startRuntimeRotationProxy) respect the same strict opt-in and that tests are
added to simulate startRuntimeRotationProxy throwing and assert the wrapper
falls back to baseContext.
- Around line 1416-1441: The per-file sync in syncShadowHomeStateBack allows
interleaved sessions to produce mixed auth files; fix by acquiring a
cross-process lease (e.g., a lock file inside originalCodexHome or a stable
multi-auth dir) before iterating SHADOW_HOME_STATE_FILES, then while holding the
lease capture the current snapshots, validate originalSnapshot equality, and
write all three files as an atomic bundle (replace per-file calls to
syncShadowHomeStateFile with a single bundled write +
tightenShadowHomePermissions on the bundle), release the lease after completion;
add a regression test that spawns two concurrent syncShadowHomeStateBack calls
against shared temp dirs and asserts the final CODEX_HOME bundle is entirely
from one source (no mixed files).
In `@test/codex-bin-wrapper.test.ts`:
- Around line 169-226: The test fixture created by
createRuntimeRotationProxyFixtureModule returns a status missing startedAt and
streamsStarted (fields defined on RuntimeRotationProxyStatus) and lacks a
negative test for startRuntimeRotationProxy failure; update the fixture module
written in test/codex-bin-wrapper.test.ts so buildStatus() includes startedAt
and streamsStarted (populate from CODEX_MULTI_AUTH_TEST_PROXY_* env vars or
sensible defaults) to match RuntimeRotationProxyStatus, and add a new test
variant that simulates the module failing to start (either by writing a module
whose startRuntimeRotationProxy throws or by omitting
dist/lib/runtime-rotation-proxy.js) and assert codex exec falls back to the
"runtime rotation proxy module is unavailable" path (check exit status and
non-proxy forwarding behavior exercised in scripts/codex.js handling around
startRuntimeRotationProxy).
In `@test/runtime-rotation-proxy.test.ts`:
- Around line 139-380: Add tests exercising the missing branches and races: (1)
add a case that authenticates using x-api-key (send header "x-api-key" equal to
clientApiKey) to hit the headers.get("x-api-key") === clientApiKey branch when
calling startRuntimeRotationProxy/postResponses; (2) create a concurrency test
that fires many simultaneous postResponses via Promise.all against
startProxy/startRuntimeRotationProxy and assert AccountManager state (no
negative budgets, unique account picks, and proxy.getStatus() counters) and that
consumeToken/refundToken paths behave deterministically; (3) add a
token-refresh-race test by using createStorage or AccountManager to set an
account's expiresAt < now and then issue two concurrent requests, asserting
ensureFreshAccessToken/queuedRefresh dedup behavior by checking the accounts
persistence mock (saveAccountsMock) was called exactly once and both requests
succeed; (4) tighten the pool-exhaustion assertion by comparing the returned
payload.error.retry_after_ms to accountManager.getMinWaitTimeForFamily(family,
model) (rather than just > 0) so writePoolExhausted regressions are caught.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 00eac433-619b-4937-9aba-a10e89965094
📒 Files selected for processing (19)
docs/configuration.mddocs/development/CONFIG_FIELDS.mddocs/features.mddocs/reference/commands.mdlib/codex-manager/commands/rotation.tslib/codex-manager/help.tslib/logger.tslib/runtime-rotation-proxy.tslib/runtime/app-bind.tsscripts/codex-app-launcher.jsscripts/codex-app-router.jsscripts/codex.jsscripts/postinstall.jstest/app-bind.test.tstest/codex-bin-wrapper.test.tstest/codex-manager-rotation-command.test.tstest/install-codex-auth.test.tstest/runtime-rotation-proxy.test.tsvitest.config.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (3)
lib/**
⚙️ CodeRabbit configuration file
focus on auth rotation, windows filesystem IO, and concurrency. verify every change cites affected tests (vitest) and that new queues handle EBUSY/429 scenarios. check for logging that leaks tokens or emails.
Files:
lib/codex-manager/help.tslib/logger.tslib/codex-manager/commands/rotation.tslib/runtime/app-bind.tslib/runtime-rotation-proxy.ts
docs/**
⚙️ CodeRabbit configuration file
keep README, SECURITY, and docs consistent with actual CLI flags and workflows. whenever behavior changes, require updated upgrade notes and mention new npm scripts.
Files:
docs/features.mddocs/development/CONFIG_FIELDS.mddocs/configuration.mddocs/reference/commands.md
test/**
⚙️ CodeRabbit configuration file
tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior. reject changes that mock real secrets or skip assertions.
Files:
test/codex-manager-rotation-command.test.tstest/app-bind.test.tstest/install-codex-auth.test.tstest/codex-bin-wrapper.test.tstest/runtime-rotation-proxy.test.ts
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:10:23.023Z
Learning: Implement account health-aware selection and automatic failover in multi-account OAuth pool management
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:10:23.023Z
Learning: Use bounded outbound request budgets to prevent a single prompt from consuming the entire account pool
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:10:23.023Z
Learning: Implement short cooldown periods instead of aggressive rotation when repeated cross-account 5xx bursts occur
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:10:23.023Z
Learning: Stagger proactive refresh operations to reduce background refresh bursts
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:10:23.023Z
Learning: Disable whole-pool replay by default when every account is rate-limited
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:10:23.023Z
Learning: Validate configuration after changes using `codex auth status`, `codex auth check`, and `codex auth forecast --live`
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:10:23.023Z
Learning: Design experimental settings features to be non-destructive by default: sync previews before apply, preserve destination-only accounts, and fail safely on backup filename collisions
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:10:35.682Z
Learning: Runtime configuration is resolved from unified settings at `~/.codex/multi-auth/settings.json`, optional override files via `CODEX_MULTI_AUTH_CONFIG_PATH`, and environment variables in a defined precedence order
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:10:35.682Z
Learning: Use stable environment overrides (`CODEX_MULTI_AUTH_DIR`, `CODEX_MULTI_AUTH_CONFIG_PATH`, `CODEX_MODE`, `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY`, `CODEX_TUI_V2`, `CODEX_TUI_COLOR_PROFILE`, `CODEX_TUI_GLYPHS`, `CODEX_AUTH_FETCH_TIMEOUT_MS`, `CODEX_AUTH_STREAM_STALL_TIMEOUT_MS`) for day-to-day workflows in most environments
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:10:35.682Z
Learning: Advanced and internal environment overrides (`CODEX_MULTI_AUTH_SYNC_CODEX_CLI`, `CODEX_MULTI_AUTH_REAL_CODEX_BIN`, `CODEX_MULTI_AUTH_BYPASS`, etc.) should only be used when debugging, controlled benchmarking, or in maintainer workflows
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:10:35.682Z
Learning: Use `codex auth status`, `codex auth list`, `codex auth check`, and `codex auth forecast --live` commands to validate effective configuration
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:10:55.586Z
Learning: When enabling `backgroundResponses=true`, ensure that requests become stateful (`store=true`) instead of stateless, and test one known `background: true` request end-to-end before rolling out to shared automation
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:10:55.586Z
Learning: Environment variable `CODEX_MULTI_AUTH_DIR` should be used to specify a custom root directory for settings, accounts, cache, and logs when needed
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:10:55.586Z
Learning: Environment variable `CODEX_MULTI_AUTH_CONFIG_PATH` can be used to specify an alternate config file path for input
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:10:55.586Z
Learning: Environment variable `CODEX_MODE` should be used to toggle Codex mode at runtime
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:10:55.586Z
Learning: Environment variable `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY` should be set to `1` or `true` to enable opt-in localhost Responses proxy for forwarded Codex sessions, or `0` or `false` to disable
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:10:55.586Z
Learning: Cross-process refresh coordination relies on lease/state files; avoid manually editing those files while the CLI is running to prevent synchronization conflicts
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:08.525Z
Learning: Store all multi-auth account state and configuration under the canonical local data root at `~/.codex/multi-auth`
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:08.525Z
Learning: Implement project-scoped account pools to allow repo-specific account state separation when needed
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:08.525Z
Learning: Support linked-worktree identity sharing to allow the same repository to share account state across worktrees
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:08.525Z
Learning: Persist quota cache to enable faster forecast and dashboard visibility between runs
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:08.525Z
Learning: Implement safe repair workflow via `codex auth fix` command to detect and repair known local storage inconsistencies
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:08.525Z
Learning: Provide diagnostics and optional repair capability via `codex auth doctor` command for inspecting and fixing common failures
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:08.525Z
Learning: Implement backup and WAL (Write-Ahead Logging) recovery in the storage runtime to ensure safer persistence when local writes are interrupted or partially applied
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:08.525Z
Learning: Support browser-first OAuth flow via `codex auth login` with manual fallback options (`--manual`, `--no-browser`, `CODEX_AUTH_NO_BROWSER=1`) for browser-restricted shells
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:08.525Z
Learning: Accept the full callback URL on stdin for manual/non-TTY login to support automation and host-managed shells without requiring local browser handoff
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:08.525Z
Learning: Implement optional plugin-host runtime integration for request transformation, token refresh, retry handling, and capability-aware account selection
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:28.456Z
Learning: Use `codex auth ...` as the canonical command family for authentication operations
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:28.456Z
Learning: Support compatibility aliases for auth commands: `codex multi auth ...`, `codex multi-auth ...`, and `codex multiauth ...`
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:28.456Z
Learning: Primary auth commands include: `codex auth login` (interactive dashboard), `codex auth status` (runtime/account summary), and `codex auth check` (account health check)
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:28.456Z
Learning: Support daily-use commands: `codex auth list`, `codex auth switch <index>`, `codex auth forecast`, and `codex auth best`
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:28.456Z
Learning: Implement repair commands: `codex auth verify-flagged`, `codex auth verify`, `codex auth fix`, `codex auth doctor`, `codex auth config explain`, `codex auth init-config`, and `codex auth debug bundle`
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:28.456Z
Learning: Support `--json` flag for machine-readable output on: verify-flagged, verify, why-selected, best, forecast, report, fix, doctor, config explain, and debug bundle
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:28.456Z
Learning: Support `--dry-run` flag on verify-flagged, verify (with `--flagged`/`--all`), fix, and doctor commands to preview changes without writing to storage
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:28.456Z
Learning: Use `--live` flag on best, forecast, report, and fix commands to enable live probe before decisions or output
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:28.456Z
Learning: Support `--explain` flag on forecast and report commands to include reasoning details in both text and JSON output
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:28.456Z
Learning: The `codex auth why-selected` command should return exit code 0 when an account is selected, and exit code 1 when no account can be selected
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:28.456Z
Learning: JSON output for `codex auth why-selected` must include: command, mode, ok, availableCount, totalCount, quotaKey, config, selected (with index, email, accountId, health, score, etc.), candidates array, and runtimeSnapshot only with `--last` flag
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:28.456Z
Learning: The `codex auth rotation` command must support: enable, disable, status, bind-app, and unbind-app subcommands to manage runtime Responses proxy
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:28.456Z
Learning: When runtime rotation is enabled, create a temporary shadow config.toml with custom provider named `codex-multi-auth-runtime-proxy` and start a 127.0.0.1 proxy on a random port
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:28.456Z
Learning: Support `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=1` environment variable to enable the proxy for the current process without changing persistent settings
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:28.456Z
Learning: The packaged desktop app bind must back up the real Codex config.toml, write the custom provider to real Codex home, start a localhost-only router, and install a user login startup entry
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:28.456Z
Learning: In non-TTY or host-managed sessions, auth flows must degrade to deterministic text behavior instead of interactive UI
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:28.456Z
Learning: Dashboard hotkey Up/Down must move selection, Enter must select/open, 1-9 must quick switch accounts, / must search, ? must toggle help, Q must go back
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:28.456Z
Learning: Account Details hotkeys: S (set current), R (refresh), E (enable/disable), D (delete), Q (back)
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:28.456Z
Learning: Settings screen hotkeys must be panel-specific with different bindings for Account List View, Summary Line, Menu Behavior, Color Theme, and Backend Controls
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:28.456Z
Learning: In non-TTY/manual shells, `codex auth login` must default to add-account mode, skip the extra 'add another account' prompt, and auto-pick the default workspace selection
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:28.456Z
Learning: Support `--manual` and `--no-browser` flags on `codex auth login` to skip browser launch and use manual callback flow
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:28.456Z
Learning: Respect `CODEX_AUTH_NO_BROWSER=1` environment variable to suppress browser launch for automation/headless sessions, but do not suppress on false-like values like 0 or false
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:28.456Z
Learning: The `codex` wrapper must route `codex auth ...` and compatibility aliases to the multi-auth runtime, and forward all other commands to official openai/codex CLI
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:28.456Z
Learning: `codex --version` must report the official openai/codex CLI version, while `codex-multi-auth --version` and `codex-multi-auth -v` must report the wrapper package version
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:44.290Z
Learning: Experimental shortcuts should include: `1` for sync preview, `2` for named backup export, `3` for toggle refresh guard, `[` or `-` to decrease refresh interval, `]` or `+` to increase refresh interval, `S` to save and return, `Q` to go back, and `A` to apply in sync review
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:44.290Z
Learning: Named backup export should prompt for filename, append `.json` when omitted, reject separators, traversal (`..`), `.rotate.`, `.tmp`, and `.wal` suffixes, and fail safely on collisions
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:44.290Z
Learning: Live account sync preview should always be shown before apply, blocked target states should not apply changes, destination active selection should be preserved, and destination-only accounts should be preserved by merge
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:44.290Z
Learning: Validation of configuration changes should be performed using `codex auth status`, `codex auth check`, and `codex auth forecast --live` commands
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:44.290Z
Learning: Stable environment overrides should support: `CODEX_MULTI_AUTH_DIR`, `CODEX_MULTI_AUTH_CONFIG_PATH`, `CODEX_MODE`, `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY`, `CODEX_MULTI_AUTH_APP_BIND_INSTALL`, `CODEX_TUI_V2`, `CODEX_TUI_COLOR_PROFILE`, `CODEX_TUI_GLYPHS`, `CODEX_AUTH_FETCH_TIMEOUT_MS`, `CODEX_AUTH_STREAM_STALL_TIMEOUT_MS`
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:49.737Z
Learning: Run `npm run build` to validate the codebase before release
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:49.737Z
Learning: Run test suite with `npm test -- test/model-map.test.ts test/request-transformer.test.ts test/codex-bin-wrapper.test.ts test/config.test.ts test/capability-policy.test.ts test/fetch-helpers.test.ts test/index.test.ts` to validate release
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:49.737Z
Learning: Implement fallback behavior in wrapper to retry with `gpt-5.4` only after real upstream unsupported-model responses on ChatGPT-backed Codex surfaces
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:49.737Z
Learning: Handle both ChatGPT unsupported-model errors and upstream no-access variant (`does not exist or you do not have access`) in wrapper retry path
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-24T18:11:49.737Z
Learning: Keep `gpt-5.5` and `gpt-5.5-pro` as first-attempt models instead of silently down-mapping ahead of time
🔇 Additional comments (6)
lib/logger.ts (1)
86-111: lgtm — sanitization logic correctly handles the new sensitive key.the normalization at
lib/logger.ts:100correctly mapsexperimental_bearer_token→experimentalbearertoken, and masking atlib/logger.ts:102will prevent bearer token leakage when app-bind config objects (containing the field fromlib/runtime/app-bind.ts:194) are logged during rotation enable/bind operations.docs/features.md (1)
27-27: lgtm.opt-in framing and the default-off note address the prior feedback; capability row is now self-explanatory without cross-referencing
lib/config.ts.docs/development/CONFIG_FIELDS.md (1)
47-47: lgtm.default-false row and env-value hint line up with
resolveBooleanSettinginlib/config.ts:780andgetCodexRuntimeRotationProxyatlib/config.ts:806. addresses the prior comment about operator ambiguity.Also applies to: 204-204
test/install-codex-auth.test.ts (1)
205-437: lgtm on launcher + postinstall gate coverage.windows redirected-desktop / onedrive test at
test/install-codex-auth.test.ts:258, macos managed-wrapper test at:280, and the postinstall gate matrix at:360lock down the new planner/gate logic nicely. deterministic, no live network.scripts/codex-app-launcher.js (1)
96-108: four prior review points cleanly addressed — LGTM.
scripts/codex-app-launcher.js:96-108now resolves onedrive-redirected desktops viaOneDrive/OneDriveConsumer/OneDriveCommercial, with the js-sidehome/Desktopas fallback.scripts/codex-app-launcher.js:255-256additionally queries[Environment]::GetFolderPath('Desktop')inside the powershell plan, which is the authoritative shell folder lookup for redirected/enterprise images.scripts/codex-app-launcher.js:300-301gates backup writes on theAlreadyManagedsentinel (our$ManagedDescriptionor our exact target/args), so a prunedapp-shortcuts.jsonno longer turns our wrapper into the "original" target on next re-route.scripts/codex-app-launcher.js:380-390usesquotePosixShellfor the mac launcher script, killing the JSON.stringify-as-shell-quoter landmine.scripts/codex-app-launcher.js:452-459wrapsJSON.parsewith rich error context including a bounded tail of stdout — no more crypticUnexpected token …on locked-down windows images.Also applies to: 255-256, 300-301, 380-390, 452-459
lib/runtime-rotation-proxy.ts (1)
129-146: lgtm — deadACCOUNT_IDre-set is gone.the previously flagged no-op guard at
lib/runtime-rotation-proxy.ts:129-146has been removed;accountIdis resolved once viaresolveAccountId(seelib/runtime-rotation-proxy.ts:683) and set once here. clean.
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
scripts/codex.js (1)
1183-1290:⚠️ Potential issue | 🔴 Criticalwindows hazard: copy-fallback path round-trips arbitrary
~/.codexfiles through utf-8 → binary corruption on sync-back
linkFileIntoShadowHome(scripts/codex.js:1268-1282) triessymlinkSyncthenlinkSync, falling back silently tocopyFileSyncviamirrorFileIntoShadowHome(scripts/codex.js:1284-1290). on windows without developer mode, file symlinks throwEPERM; hardlinks fail across volumes. so on a typical windows install, every non-state file in~/.codexends up as an independent copy in the shadow home.cleanup then walks the shadow with
collectShadowHomeSyncFileNames(scripts/codex.js:1292-1312), captures both shadow and original viacaptureShadowHomeStatewhich readsreadFileSync(filePath, "utf8")(scripts/codex.js:1193), and on mismatch writes the utf-8 string back to the original viasyncShadowHomeStateFile(scripts/codex.js:1399). for any binary file in~/.codex(sqlite chat history, packed caches, lock files with non-text bytes, plugin artifacts, etc.) this is a lossy utf-8 round-trip — replacement chars on read, mojibake on write, file silently destroyed.state files (
auth.json,accounts.json,.codex-global-state.json) are json so the round-trip happens to be safe, but the discovery loop atscripts/codex.js:1331-1374deliberately includes arbitrary non-state files (symlink-or-copy viamirrorFileIntoShadowHome) andrememberSyncFile(name)adds them to the sync-back set at line 1359 regardless. that's the bug surface.fix via
Buffer(no encoding) instead of utf-8:
- update
captureShadowHomeStateto read without encoding (returns Buffer)- update
shadowHomeStateMatchesto useBuffer.equals()for byte comparisonsyncShadowHomeStateFilealready usescopyFileSyncso it's encoding-agnosticadd a vitest fixture in
test/shadow-home-mirror.test.tsthat:
- forces
linkFileIntoShadowHometo returnfalse(stub the link calls)- seeds
~/.codex/foo.binwith non-utf-8 bytes (Buffer.from([0xff, 0xfe, 0x00, 0x80]))- runs a roundtrip through
createShadowHomeMirror+ cleanup- asserts the original bytes survive byte-for-byte intact
current test at
test/codex-bin-wrapper.test.tsuses only text files (state_5.sqlite written as"state\n"with utf-8), so it never exercises the copy-fallback corruption path.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/codex.js` around lines 1183 - 1290, captureShadowHomeState currently reads files with readFileSync(..., "utf8") which corrupts binary files when mirror fallback copies are synced back; change captureShadowHomeState to read raw Buffers (omit encoding) and update shadowHomeStateMatches to compare using Buffer.equals(left.content, right.content) (ensuring you coerce/handle null/unreadable cases); syncShadowHomeStateFile can remain as-is since it copies bytes; add a vitest in test/shadow-home-mirror.test.ts that stubs linkFileIntoShadowHome to return false, seeds a binary file (Buffer.from([0xff,0xfe,0x00,0x80])) in the real ~/.codex fixture, runs createShadowHomeMirror + cleanup, and asserts the original bytes survive unchanged.
♻️ Duplicate comments (8)
lib/runtime-rotation-proxy.ts (6)
148-154:⚠️ Potential issue | 🟡 Minor
isAuthorizedClientstill uses===— swap totimingSafeEqual.
lib/runtime-rotation-proxy.ts:152-153compares both the bearer token andx-api-keyagainstclientApiKeywith===. proxy is bound to127.0.0.1so practical attack surface is small, but any local process on loopback can probe and the fix is onenode:cryptoimport. defense-in-depth is cheap on an auth gate that protects token rotation state.🛡️ drop-in replacement
+import { timingSafeEqual } from "node:crypto"; + +function safeEqual(a: string, b: string): boolean { + const aBuf = Buffer.from(a); + const bBuf = Buffer.from(b); + if (aBuf.length !== bBuf.length) return false; + return timingSafeEqual(aBuf, bBuf); +} + function isAuthorizedClient(headers: Headers, clientApiKey: string | null): boolean { if (!clientApiKey) return true; const authorization = headers.get("authorization") ?? ""; const bearerMatch = authorization.match(/^Bearer\s+(.+)$/i); - if (bearerMatch?.[1]?.trim() === clientApiKey) return true; - return headers.get("x-api-key") === clientApiKey; + const bearer = bearerMatch?.[1]?.trim(); + if (bearer && safeEqual(bearer, clientApiKey)) return true; + const apiKey = headers.get("x-api-key"); + return typeof apiKey === "string" && safeEqual(apiKey, clientApiKey); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/runtime-rotation-proxy.ts` around lines 148 - 154, Replace the direct string equality checks in isAuthorizedClient with a timing-safe comparison: import timingSafeEqual from node:crypto, turn both operands into Buffer instances (e.g., Buffer.from(token, 'utf8')) and only call timingSafeEqual when the two buffers have identical lengths, falling back to false if lengths differ; apply this to the bearer token comparison (bearerMatch?.[1]) and the x-api-key header comparison while preserving the existing early-return behavior when clientApiKey is null/undefined.
724-745:⚠️ Potential issue | 🟠 Majorfetch + stream still have no timeouts; one stuck upstream burns the whole pool.
lib/runtime-rotation-proxy.ts:727-731callsfetchImplwith nosignal, andforwardStreamingResponseatlib/runtime-rotation-proxy.ts:583-590reads in a barewhile (true)loop with no stall watchdog. a hung codex responses endpoint will hold the account, the socket, and the client indefinitely — and because the per-request loop walks the full pool (lib/runtime-rotation-proxy.ts:664), a single stuck request can serially burn every account's quota.lib/config.tsalready exposesgetFetchTimeoutMs(pluginConfig)andgetStreamStallTimeoutMs(pluginConfig)for exactly this purpose.wire both:
- wrap
fetchImplwith anAbortControllerfed bygetFetchTimeoutMs. the existing catch atlib/runtime-rotation-proxy.ts:732-745already handles abort by refunding + cooling down + rotating.- add a stall timer in
forwardStreamingResponsethat resets per chunk and firesonStreamError()aftergetStreamStallTimeoutMselapses with no data.- add a vitest in
test/runtime-rotation-proxy.test.tsthat stubsfetchImplto never resolve and asserts pool-exhausted fires within the timeout window.As per coding guidelines for
lib/**: "focus on auth rotation, windows filesystem IO, and concurrency. verify every change cites affected tests (vitest) and that new queues handle EBUSY/429 scenarios."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/runtime-rotation-proxy.ts` around lines 724 - 745, The fetch and streaming paths in runtime-rotation-proxy lack timeouts: wrap the fetchImpl call in the request loop (the block that assigns upstream in the rotation loop) with an AbortController wired to getFetchTimeoutMs(pluginConfig) so fetchImpl receives a signal and aborts on timeout (so the existing catch path that refunds and cools down still applies), add a per-chunk stall watchdog in forwardStreamingResponse that resets whenever a chunk is received and calls onStreamError()/aborts the stream after getStreamStallTimeoutMs(pluginConfig) elapses with no data, and add a vitest in test/runtime-rotation-proxy.test.ts that stubs fetchImpl to never resolve and asserts the pool exhaustion/rotation behavior triggers within the configured timeout; reference the symbols fetchImpl, forwardStreamingResponse, getFetchTimeoutMs, getStreamStallTimeoutMs, onStreamError, and accountManager to locate and modify the code.
220-226:⚠️ Potential issue | 🟠 Major
readRequestBodystill has no size cap — local DoS via loopback.
lib/runtime-rotation-proxy.ts:220-226drains the entire request into memory with no ceiling. whenclientApiKeyis unset (the default atlib/runtime-rotation-proxy.ts:609-613), any local process that can reach loopback can post an unbounded body and exhaust heap before the body even hits the upstream selector. add a 64 MiB ceiling and respond413when exceeded.🛡️ proposed cap
+const MAX_REQUEST_BODY_BYTES = 64 * 1024 * 1024; + async function readRequestBody(req: IncomingMessage): Promise<Buffer> { const chunks: Buffer[] = []; + let total = 0; for await (const chunk of req) { - chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); + const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); + total += buf.length; + if (total > MAX_REQUEST_BODY_BYTES) { + throw Object.assign(new Error("request body too large"), { + statusCode: 413, + }); + } + chunks.push(buf); } return Buffer.concat(chunks); }then map the thrown
statusCodein the outer catch atlib/runtime-rotation-proxy.ts:853-865to a413response, and add a vitest intest/runtime-rotation-proxy.test.tsthat posts an oversized body and asserts the413payload.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/runtime-rotation-proxy.ts` around lines 220 - 226, The readRequestBody function currently reads the whole request into memory with no cap; modify readRequestBody to enforce a 64 MiB (67_108_864 bytes) maximum while accumulating chunks and throw an error (e.g., attach statusCode = 413) when the limit is exceeded, so it stops consuming the request; update the outer error handling logic in the runtime-rotation-proxy request flow (the catch that converts thrown errors into responses) to translate that thrown statusCode into an HTTP 413 response; add a vitest in test/runtime-rotation-proxy.test.ts that POSTS a body larger than 64 MiB and asserts the proxy returns 413.
768-797:⚠️ Potential issue | 🟡 Minor401 still doesn't refund the consumed token, but 5xx does — bookkeeping divergence persists.
lib/runtime-rotation-proxy.ts:768-781records failure + auth-failure cooldown but skipsaccountManager.refundToken(...). the 5xx branch immediately below atlib/runtime-rotation-proxy.ts:783-797does refund. both branches represent "we did not serve the client", so token bookkeeping should match. either mirror the 5xx refund here or leave a one-line comment ("quota considered consumed by upstream when it returned 401") so a future contributor doesn't accidentally normalize one of the two.same reminder applies to the 429 branch at
lib/runtime-rotation-proxy.ts:747-766— likely intentional that quota was genuinely used, but worth stating once.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/runtime-rotation-proxy.ts` around lines 768 - 797, The 401 (HTTP_STATUS.UNAUTHORIZED) branch in the rotation logic (within the block that calls readErrorBody, accountManager.recordFailure, accountManager.markAccountCoolingDown, accountManager.saveToDiskDebounced and updates exhaustionReason/status.retries/status.rotations) does not call accountManager.refundToken like the 5xx branch; either add a call to accountManager.refundToken(refreshed.account, context.family, context.model) in that 401 branch to keep token bookkeeping consistent, or if the intent is that quota is consumed on 401, add a one-line comment above the 401 branch explaining that choice (and likewise add a clarifying comment in the 429 branch) so future contributors won’t normalize the behavior accidentally.
664-666:⚠️ Potential issue | 🟡 Minorper-request rotation budget still equals full pool — single prompt can drain every account.
lib/runtime-rotation-proxy.ts:664still usesaccountManager.getAccountCount()as the ceiling, so on a flapping upstream a single user prompt walks every account in sequence (each one paying network/fetch latency, cooldown, and refund overhead). combined with the missing fetch timeout above, this is the worst-case path. cap per-request rotation at e.g.min(getAccountCount(), 5)and emitpool_exhaustedwithreason: "budget"once that cap is hit, while keepinggetAccountCount()as the absolute safety bound.As per coding guidelines for
**/*.{js,ts,mjs,cjs}: "implement bounded outbound request budget per prompt to prevent indefinite pool walking."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/runtime-rotation-proxy.ts` around lines 664 - 666, The per-request rotation loop in runtime-rotation-proxy currently uses accountManager.getAccountCount() as the dynamic ceiling, allowing a single prompt to iterate the whole pool; change the loop ceiling to a bounded budget (e.g., const rotationBudget = Math.min(accountManager.getAccountCount(), 5)) and use attemptedIndexes.size < rotationBudget in place of Math.max(1, accountManager.getAccountCount()); after hitting the budget emit the existing pool_exhausted event with { reason: "budget" } and break out (while still keeping accountManager.getAccountCount() as an absolute safety bound elsewhere), and ensure this logic wraps where chooseAccount(...) and attemptedIndexes are used to prevent full-pool walking per prompt.
39-52:⚠️ Potential issue | 🟠 Major
lastAccountEmailis still on the public status surface — PII leak path remains open.
lib/runtime-rotation-proxy.ts:49keeps the raw email on the publicRuntimeRotationProxyStatus, andlib/runtime-rotation-proxy.ts:181+lib/runtime-rotation-proxy.ts:187write it both to the in-memory snapshot and to the persisted runtime-observability snapshot. anything that picks upgetStatus()(the rotation app helper status file atruntime-rotation-app-helper.json, the app-bind status file referenced in the PR summary, or any debug bundle scooping observability state) lands raw account emails on disk. index + label + accountId already uniquely identify a rotated account for operator use.
test/runtime-rotation-proxy.test.ts:208-213currently pins this exact field in place — pair this fix with that test update.🛡️ drop email from the persisted status surface
export interface RuntimeRotationProxyStatus { … lastAccountIndex: number | null; lastAccountLabel: string | null; - lastAccountEmail: string | null; lastAccountId: string | null; lastAccountUpdatedAt: number | null; } … status.lastAccountLabel = identity.label; - status.lastAccountEmail = identity.email; status.lastAccountId = identity.accountId; … mutateRuntimeObservabilitySnapshot((snapshot) => { snapshot.lastAccountIndex = identity.index; snapshot.lastAccountLabel = identity.label; - snapshot.lastAccountEmail = identity.email; snapshot.lastAccountId = identity.accountId; snapshot.lastAccountUpdatedAt = identity.updatedAt; });As per coding guidelines for
lib/**: "check for logging that leaks tokens or emails."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/runtime-rotation-proxy.ts` around lines 39 - 52, The public status type and snapshot writing still include the PII field lastAccountEmail, so remove lastAccountEmail from the RuntimeRotationProxyStatus interface and stop writing it in the snapshot and persisted status paths (referencing RuntimeRotationProxyStatus, getStatus(), and where snapshots are built in runtime-rotation-proxy methods that set lastAccountEmail); instead persist only lastAccountIndex, lastAccountLabel, lastAccountId and lastAccountUpdatedAt to uniquely identify accounts. Also update the test runtime-rotation-proxy.test.ts (the assertions around lines referencing lastAccountEmail) to no longer expect or assert the email field. Ensure no other logging or status serialization writes the raw email.test/codex-bin-wrapper.test.ts (1)
196-209:⚠️ Potential issue | 🟡 Minorfixture status still drifts from
RuntimeRotationProxyStatus— missingstartedAtandstreamsStarted.the previous review flagged that
getStatus: () => ({})did not satisfy the contract atlib/runtime-rotation-proxy.ts:39-52. the newbuildStatus()covers most fields but still omitsstartedAtandstreamsStarted, which the real status object exposes (seelib/runtime-rotation-proxy.ts:626-639and the streaming bump atlib/runtime-rotation-proxy.ts:566). if scripts/codex.js or the app-helper ever start reading either of those (e.g. for uptime or stream telemetry), the wrapper tests will silently pass against a stub that doesn't reflect production shape.🛡️ proposed fix
"function buildStatus() {", " return {", + " startedAt: readOptionalNumberEnv('CODEX_MULTI_AUTH_TEST_PROXY_STARTED_AT') ?? 0,", " totalRequests: readOptionalNumberEnv('CODEX_MULTI_AUTH_TEST_PROXY_REQUESTS') ?? 0,", " upstreamRequests: 0,", " retries: 0,", " rotations: readOptionalNumberEnv('CODEX_MULTI_AUTH_TEST_PROXY_ROTATIONS') ?? 0,", + " streamsStarted: readOptionalNumberEnv('CODEX_MULTI_AUTH_TEST_PROXY_STREAMS_STARTED') ?? 0,", " lastAccountIndex: readOptionalNumberEnv('CODEX_MULTI_AUTH_TEST_PROXY_LAST_ACCOUNT_INDEX'),",As per coding guidelines for
test/**: "demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/codex-bin-wrapper.test.ts` around lines 196 - 209, The test fixture buildStatus() is missing the startedAt and streamsStarted fields required to match RuntimeRotationProxyStatus; update buildStatus (in test/codex-bin-wrapper.test.ts) to include startedAt (use a numeric timestamp or null, e.g. readOptionalNumberEnv(...) ?? null) and streamsStarted (default to 0) so the stub shape matches the real object returned by RuntimeRotationProxy (see buildStatus, getStatus and runtime-rotation-proxy usages like streamsStarted and startedAt).test/runtime-rotation-proxy.test.ts (1)
138-424:⚠️ Potential issue | 🟠 Majorstill missing the regression cases the past review flagged.
re-checking the suite against the past review's three open items and
test/**guidelines ("demand regression cases that reproduce concurrency bugs, token refresh races..."):
x-api-key client auth path untested.
lib/runtime-rotation-proxy.ts:153acceptsx-api-key: <clientApiKey>as an alternate auth header. the only auth test attest/runtime-rotation-proxy.test.ts:139-174exercises Bearer only. add anaccepts x-api-key client tokenscase and confirm Bearer +x-api-keyare independently honored.no concurrent inbound requests.
lib/runtime-rotation-proxy.ts:868-870dispatcheshandleRequestper connection and mutates sharedstatus(lastError,lastAccountIndex) at every await boundary. fire NpostResponsesviaPromise.alland assert counters reconcile (no negativeupstreamRequests, no duplicate active-account writes viawithAccountStorageTransactionMock).token refresh race untested. every fixture account at
test/runtime-rotation-proxy.test.ts:46hasexpiresAt: now + 3_600_000, soensureFreshAccessToken(lib/runtime-rotation-proxy.ts:323-373) never fires. add a case withexpiresAt < now, two simultaneouspostResponses, and assertqueuedRefreshdedupes (singlecommitRefreshedAuthcall) and both responses succeed.pool-exhaustion
retry_after_msis asserted loosely.test/runtime-rotation-proxy.test.ts:394only checks> 0; the contract atlib/runtime-rotation-proxy.ts:547returnsaccountManager.getMinWaitTimeForFamily(family, model). assert exact equality so a future change towritePoolExhausteddoesn't go unnoticed.As per coding guidelines for
test/**: "demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/runtime-rotation-proxy.test.ts` around lines 138 - 424, Add three regression tests and tighten the pool-exhaustion assertion: (1) add an "accepts x-api-key client tokens" test that posts to postResponses using header "x-api-key: runtime-secret" and verify it is accepted independently of Bearer auth (reference startRuntimeRotationProxy / clientApiKey and the existing Bearer test at runtime-rotation-proxy tests); (2) add a concurrency test that fires N simultaneous postResponses via Promise.all against startProxy and assert proxy.getStatus() counters (upstreamRequests, lastAccountIndex/lastAccountEmail), no negative upstreamRequests, and that withAccountStorageTransactionMock was not invoked multiple duplicate times (reference handleRequest and withAccountStorageTransactionMock); (3) add a token-refresh race test by creating accounts with expiresAt < now, issue two simultaneous postResponses, assert both succeed and that commitRefreshedAuth (or the exported commit refresh hook used in ensureFreshAccessToken) was called exactly once (reference ensureFreshAccessToken / queuedRefresh); and (4) change the pool-exhaustion test to assert payload.error.retry_after_ms equals accountManager.getMinWaitTimeForFamily("codex","gpt-5-codex") instead of just > 0 so the exact contract is enforced (reference writePoolExhausted and getMinWaitTimeForFamily).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/runtime-rotation-proxy.ts`:
- Around line 198-202: The code currently calls accountManager.saveToDisk()
synchronously after marking switches for MODEL_FAMILIES, causing high write
amplification; change that call to accountManager.saveToDiskDebounced() to
coalesce writes (keep the loop and markSwitched calls as-is). Also evaluate
whether syncCodexCliActiveSelectionForIndex(account.index) truly needs immediate
persistence; if not, remove or defer it (e.g., call it from the debounced save
completion or omit it) so the request path avoids the extra immediate disk
write.
- Around line 193-207: persistRuntimeActiveAccount currently iterates
MODEL_FAMILIES and calls accountManager.markSwitched(account, "rotation",
family) for every family; change it to only call markSwitched for the family
actually served (use a new parameter family: ModelFamily in
persistRuntimeActiveAccount and call accountManager.markSwitched(account,
"rotation", family) only for that family) and update the site that invokes
persistRuntimeActiveAccount (the call that has access to context.family) to pass
context.family so only the served family’s currentAccountIndexByFamily is
stamped.
In `@scripts/codex.js`:
- Around line 425-482: The pendingAccountReadIds set in
createAppServerAccountReadProtocolProxy can grow unbounded because ids are only
removed when a response with a result arrives; modify the bookkeeping to (1)
soft-cap the set (e.g., if (pendingAccountReadIds.size > 4096) {
pendingAccountReadIds.clear(); console.error(...) }) or implement a TTL map
(Map<key, expiresAt>) and prune on insert to avoid leaks, and (2) update
rewriteAppServerAccountReadResponseLine to delete the id for both JSON-RPC
"result" and "error" responses (or check Object.hasOwn(message, "error")
alongside "result") and reorder early returns so the removal from
pendingAccountReadIds happens before returning; use the existing jsonRpcIdKey,
pendingAccountReadIds, createAppServerAccountReadProtocolProxy, and
rewriteAppServerAccountReadResponseLine symbols to locate and change the logic.
In `@test/codex-manager-cli.test.ts`:
- Around line 6883-6948: The test claims "before rendering" but never asserts
call order; update the test after calling runCodexMultiAuthCli to assert that
setCodexCliActiveSelectionMock ran before promptLoginModeMock by comparing their
mock.invocationCallOrder values (e.g., get the first invocationCallOrder entry
for setCodexCliActiveSelectionMock and for promptLoginModeMock and
expect(setOrder).toBeLessThan(promptOrder)); keep existing expectations but add
this ordering assertion to ensure the sync happens prior to rendering/prompting.
- Around line 946-954: Add a deterministic regression test case mirroring the
existing "intentional-reset" mock but using Windows-style backslash paths so the
intentional-reset branch output remains correct on Windows; locate the test near
inspectStorageHealthMock.mockResolvedValueOnce in test/codex-manager-cli.test.ts
and add another mockResolvedValueOnce with state: "intentional-reset" and
path/resetMarkerPath/walPath values containing backslashes (e.g.
"C:\\mock\\openai-codex-accounts.json" and the corresponding
".intentional-reset" and ".wal" variants), then assert the CLI status output for
the intentional reset case matches the expected Windows path formatting. Ensure
the test uses vitest utilities and stays deterministic (no real secrets or
skipped assertions).
In `@test/runtime-rotation-proxy.test.ts`:
- Around line 208-213: The test currently asserts a concrete PII value from
proxy.getStatus() (lastAccountEmail) which pins PII leakage behavior; update the
assertion to enforce a redacted contract instead: remove the lastAccountEmail
key from the expected object or replace that line with a negative assertion like
expect(proxy.getStatus()).not.toHaveProperty("lastAccountEmail") (or assert a
hashed/redacted form) so the test matches the intended behavior
implemented/expected in lib/runtime-rotation-proxy.ts (see handling around
runtime-rotation-proxy.ts:49) rather than asserting the raw email.
---
Outside diff comments:
In `@scripts/codex.js`:
- Around line 1183-1290: captureShadowHomeState currently reads files with
readFileSync(..., "utf8") which corrupts binary files when mirror fallback
copies are synced back; change captureShadowHomeState to read raw Buffers (omit
encoding) and update shadowHomeStateMatches to compare using
Buffer.equals(left.content, right.content) (ensuring you coerce/handle
null/unreadable cases); syncShadowHomeStateFile can remain as-is since it copies
bytes; add a vitest in test/shadow-home-mirror.test.ts that stubs
linkFileIntoShadowHome to return false, seeds a binary file
(Buffer.from([0xff,0xfe,0x00,0x80])) in the real ~/.codex fixture, runs
createShadowHomeMirror + cleanup, and asserts the original bytes survive
unchanged.
---
Duplicate comments:
In `@lib/runtime-rotation-proxy.ts`:
- Around line 148-154: Replace the direct string equality checks in
isAuthorizedClient with a timing-safe comparison: import timingSafeEqual from
node:crypto, turn both operands into Buffer instances (e.g., Buffer.from(token,
'utf8')) and only call timingSafeEqual when the two buffers have identical
lengths, falling back to false if lengths differ; apply this to the bearer token
comparison (bearerMatch?.[1]) and the x-api-key header comparison while
preserving the existing early-return behavior when clientApiKey is
null/undefined.
- Around line 724-745: The fetch and streaming paths in runtime-rotation-proxy
lack timeouts: wrap the fetchImpl call in the request loop (the block that
assigns upstream in the rotation loop) with an AbortController wired to
getFetchTimeoutMs(pluginConfig) so fetchImpl receives a signal and aborts on
timeout (so the existing catch path that refunds and cools down still applies),
add a per-chunk stall watchdog in forwardStreamingResponse that resets whenever
a chunk is received and calls onStreamError()/aborts the stream after
getStreamStallTimeoutMs(pluginConfig) elapses with no data, and add a vitest in
test/runtime-rotation-proxy.test.ts that stubs fetchImpl to never resolve and
asserts the pool exhaustion/rotation behavior triggers within the configured
timeout; reference the symbols fetchImpl, forwardStreamingResponse,
getFetchTimeoutMs, getStreamStallTimeoutMs, onStreamError, and accountManager to
locate and modify the code.
- Around line 220-226: The readRequestBody function currently reads the whole
request into memory with no cap; modify readRequestBody to enforce a 64 MiB
(67_108_864 bytes) maximum while accumulating chunks and throw an error (e.g.,
attach statusCode = 413) when the limit is exceeded, so it stops consuming the
request; update the outer error handling logic in the runtime-rotation-proxy
request flow (the catch that converts thrown errors into responses) to translate
that thrown statusCode into an HTTP 413 response; add a vitest in
test/runtime-rotation-proxy.test.ts that POSTS a body larger than 64 MiB and
asserts the proxy returns 413.
- Around line 768-797: The 401 (HTTP_STATUS.UNAUTHORIZED) branch in the rotation
logic (within the block that calls readErrorBody, accountManager.recordFailure,
accountManager.markAccountCoolingDown, accountManager.saveToDiskDebounced and
updates exhaustionReason/status.retries/status.rotations) does not call
accountManager.refundToken like the 5xx branch; either add a call to
accountManager.refundToken(refreshed.account, context.family, context.model) in
that 401 branch to keep token bookkeeping consistent, or if the intent is that
quota is consumed on 401, add a one-line comment above the 401 branch explaining
that choice (and likewise add a clarifying comment in the 429 branch) so future
contributors won’t normalize the behavior accidentally.
- Around line 664-666: The per-request rotation loop in runtime-rotation-proxy
currently uses accountManager.getAccountCount() as the dynamic ceiling, allowing
a single prompt to iterate the whole pool; change the loop ceiling to a bounded
budget (e.g., const rotationBudget = Math.min(accountManager.getAccountCount(),
5)) and use attemptedIndexes.size < rotationBudget in place of Math.max(1,
accountManager.getAccountCount()); after hitting the budget emit the existing
pool_exhausted event with { reason: "budget" } and break out (while still
keeping accountManager.getAccountCount() as an absolute safety bound elsewhere),
and ensure this logic wraps where chooseAccount(...) and attemptedIndexes are
used to prevent full-pool walking per prompt.
- Around line 39-52: The public status type and snapshot writing still include
the PII field lastAccountEmail, so remove lastAccountEmail from the
RuntimeRotationProxyStatus interface and stop writing it in the snapshot and
persisted status paths (referencing RuntimeRotationProxyStatus, getStatus(), and
where snapshots are built in runtime-rotation-proxy methods that set
lastAccountEmail); instead persist only lastAccountIndex, lastAccountLabel,
lastAccountId and lastAccountUpdatedAt to uniquely identify accounts. Also
update the test runtime-rotation-proxy.test.ts (the assertions around lines
referencing lastAccountEmail) to no longer expect or assert the email field.
Ensure no other logging or status serialization writes the raw email.
In `@test/codex-bin-wrapper.test.ts`:
- Around line 196-209: The test fixture buildStatus() is missing the startedAt
and streamsStarted fields required to match RuntimeRotationProxyStatus; update
buildStatus (in test/codex-bin-wrapper.test.ts) to include startedAt (use a
numeric timestamp or null, e.g. readOptionalNumberEnv(...) ?? null) and
streamsStarted (default to 0) so the stub shape matches the real object returned
by RuntimeRotationProxy (see buildStatus, getStatus and runtime-rotation-proxy
usages like streamsStarted and startedAt).
In `@test/runtime-rotation-proxy.test.ts`:
- Around line 138-424: Add three regression tests and tighten the
pool-exhaustion assertion: (1) add an "accepts x-api-key client tokens" test
that posts to postResponses using header "x-api-key: runtime-secret" and verify
it is accepted independently of Bearer auth (reference startRuntimeRotationProxy
/ clientApiKey and the existing Bearer test at runtime-rotation-proxy tests);
(2) add a concurrency test that fires N simultaneous postResponses via
Promise.all against startProxy and assert proxy.getStatus() counters
(upstreamRequests, lastAccountIndex/lastAccountEmail), no negative
upstreamRequests, and that withAccountStorageTransactionMock was not invoked
multiple duplicate times (reference handleRequest and
withAccountStorageTransactionMock); (3) add a token-refresh race test by
creating accounts with expiresAt < now, issue two simultaneous postResponses,
assert both succeed and that commitRefreshedAuth (or the exported commit refresh
hook used in ensureFreshAccessToken) was called exactly once (reference
ensureFreshAccessToken / queuedRefresh); and (4) change the pool-exhaustion test
to assert payload.error.retry_after_ms equals
accountManager.getMinWaitTimeForFamily("codex","gpt-5-codex") instead of just >
0 so the exact contract is enforced (reference writePoolExhausted and
getMinWaitTimeForFamily).
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 4bd97771-63a8-474a-b417-d70f8e9ae301
📒 Files selected for processing (6)
lib/codex-manager.tslib/runtime-rotation-proxy.tsscripts/codex.jstest/codex-bin-wrapper.test.tstest/codex-manager-cli.test.tstest/runtime-rotation-proxy.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,ts,mjs,cjs}
📄 CodeRabbit inference engine (README.md)
Store multi-auth settings in
~/.codex/multi-auth/settings.jsonor override withCODEX_MULTI_AUTH_DIRenvironment variableStore OAuth accounts in
~/.codex/multi-auth/openai-codex-accounts.jsonwith per-project accounts under~/.codex/multi-auth/projects/<project-key>/openai-codex-accounts.jsonRespect environment variable overrides for runtime configuration:
CODEX_MULTI_AUTH_DIR,CODEX_MULTI_AUTH_CONFIG_PATH,CODEX_MODE,CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY,CODEX_MULTI_AUTH_APP_ROTATION_IDLE_MS,CODEX_MULTI_AUTH_APP_BIND_INSTALL,CODEX_MULTI_AUTH_APP_LAUNCHER_INSTALL,CODEX_TUI_V2,CODEX_TUI_COLOR_PROFILE,CODEX_TUI_GLYPHS,CODEX_AUTH_BACKGROUND_RESPONSES,CODEX_AUTH_FETCH_TIMEOUT_MS,CODEX_AUTH_STREAM_STALL_TIMEOUT_MSImplement account health-aware selection and automatic failover for the OAuth account pool
Disable whole-pool replay by default when every account is rate-limited; implement bounded outbound request budget per prompt to prevent indefinite pool walking
Trigger cooldown instead of aggressive rotation on repeated cross-account 5xx bursts
Stagger proactive refresh to reduce background refresh bursts
Implement flagged account verification and restore flow for previously flagged accounts
Support session affinity and live account sync controls for account switching during runtime
Expose runtime counters, budget/cooldown state, and multi-auth probe visibility in
codex auth statusandcodex auth reportcommandsEnsure Codex-oriented request/prompt compatibility with strict runtime handling for forwarded Codex CLI/app sessions
Enable
backgroundResponsesin settings or setCODEX_AUTH_BACKGROUND_RESPONSES=1only for callers that intentionally sendbackground: truein requestsPreserve destination-only accounts and fail safely on backup filename collisions in experimental settings
Files:
lib/codex-manager.tstest/codex-manager-cli.test.tstest/codex-bin-wrapper.test.tstest/runtime-rotation-proxy.test.tsscripts/codex.jslib/runtime-rotation-proxy.ts
lib/**
⚙️ CodeRabbit configuration file
focus on auth rotation, windows filesystem IO, and concurrency. verify every change cites affected tests (vitest) and that new queues handle EBUSY/429 scenarios. check for logging that leaks tokens or emails.
Files:
lib/codex-manager.tslib/runtime-rotation-proxy.ts
test/**
⚙️ CodeRabbit configuration file
tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior. reject changes that mock real secrets or skip assertions.
Files:
test/codex-manager-cli.test.tstest/codex-bin-wrapper.test.tstest/runtime-rotation-proxy.test.ts
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:10:22.007Z
Learning: Validate configuration after changes using `codex auth status`, `codex auth check`, and `codex auth forecast --live` commands
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:10:30.232Z
Learning: Plugin runtime config source selection follows this order: (1) Unified settings `pluginConfig` from `settings.json` when present and valid, (2) Fallback file config from `CODEX_MULTI_AUTH_CONFIG_PATH` when unified settings are absent/invalid, (3) Hardcoded defaults; environment variables override individual runtime settings after source selection
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:10:30.232Z
Learning: Keep recommended defaults enabled: `menuAutoFetchLimits`, `menuSortEnabled`, `liveAccountSync`, `sessionAffinity`, `proactiveRefreshGuardian`, and `preemptiveQuotaEnabled` for most environments
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:10:30.232Z
Learning: Use stable environment overrides (`CODEX_MULTI_AUTH_DIR`, `CODEX_MULTI_AUTH_CONFIG_PATH`, `CODEX_MODE`, etc.) for day-to-day workflows and reserve advanced/internal overrides for debugging and maintainer workflows only
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:10:30.232Z
Learning: Validate effective configuration using `codex auth status`, `codex auth list`, `codex auth check`, and `codex auth forecast --live` commands
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:10:49.773Z
Learning: When enabling `backgroundResponses` compatibility switch, ensure that stateless pipelines are not affected by forced `store=true`, preserve input item IDs, and test one known `background: true` request end-to-end before rolling out across shared automation
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:10:49.773Z
Learning: Cross-process refresh coordination must use lease/state files and should not be manually edited while the CLI is running
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:10:49.773Z
Learning: Backup and WAL artifacts may exist briefly during writes and recovery as part of normal safety behavior and should not be treated as errors
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:10:59.674Z
Learning: Store canonical local data under `~/.codex/multi-auth`
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:10:59.674Z
Learning: Support project-scoped account pools for repo-specific account state separation
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:10:59.674Z
Learning: Implement linked-worktree identity sharing to allow the same repository to share account state across worktrees
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:10:59.674Z
Learning: Persist quota cache between runs for faster forecast and dashboard visibility
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:10:59.674Z
Learning: Implement browser-first OAuth flow with manual fallback using `--manual`, `--no-browser`, and `CODEX_AUTH_NO_BROWSER=1` environment variable
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:10:59.674Z
Learning: Support manual/non-TTY login that accepts full callback URL on stdin for automation and host-managed shells
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:10:59.674Z
Learning: Implement safe repair workflow in `codex auth fix` to detect and repair known local storage inconsistencies
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:10:59.674Z
Learning: Provide diagnostics and optional repair capabilities via `codex auth doctor` command
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:10:59.674Z
Learning: Implement backup and WAL (Write-Ahead Logging) recovery in storage runtime for safer persistence during interrupted or partial writes
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:10:59.674Z
Learning: Support optional plugin runtime that uses the same account pool for request transformation, token refresh, retry/cooldown handling, session affinity, and quota-aware account selection
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:11:14.839Z
Learning: Primary operations use `codex auth ...` command. Compatibility aliases are supported: `codex multi auth ...`, `codex multi-auth ...`, `codex multiauth ...`
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:11:14.839Z
Learning: Use `codex auth login` to open interactive auth dashboard; support `--manual` or `--no-browser` flags to skip browser launch and use manual callback flow
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:11:14.839Z
Learning: Use `codex auth list` to list saved accounts and active account, and `codex auth switch <index>` to set active account by index
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:11:14.839Z
Learning: Use `codex auth verify` as the single entry point for installation self-tests, supporting `--paths`, `--flagged`, and `--all` modes; use `--json` for machine-readable output
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:11:14.839Z
Learning: Use `codex auth why-selected [--now|--last] [--json]` to explain which account the rotation selector would pick with per-candidate scoring
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:11:14.839Z
Learning: Use `codex auth rotation` subcommands (enable, disable, status, bind-app, unbind-app) to manage the opt-in runtime Responses proxy for live Codex account rotation
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:11:14.839Z
Learning: Enable runtime rotation via `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=1` environment variable for the current process without changing settings
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:11:14.839Z
Learning: When rotation is enabled, create a temporary shadow `CODEX_HOME/config.toml` with custom provider named `codex-multi-auth-runtime-proxy` and start a `127.0.0.1` proxy on a random port
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:11:14.839Z
Learning: For non-TTY sessions (including `CODEX_TUI=1`, `CODEX_DESKTOP=1`, `TERM_PROGRAM=codex`, `ELECTRON_RUN_AS_NODE=1`), auth flows should degrade to deterministic text behavior with defaults for workspace selection
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:11:14.839Z
Learning: In non-TTY/manual shells, accept the full OAuth redirect URL on stdin, for example: `echo "http://127.0.0.1:1455/auth/callback?code=..." | codex auth login --manual`
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:11:14.839Z
Learning: `codex --version` should report the official `openai/codex` CLI version; `codex-multi-auth --version` and `codex-multi-auth -v` should report the wrapper package version
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:11:14.839Z
Learning: Use structured JSON output format with `--json` flag on commands like `verify-flagged`, `verify`, `why-selected`, `best`, `forecast`, `report`, `fix`, `doctor`, `config explain`, and `debug bundle`
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:11:14.839Z
Learning: Use `--explain` flag with `forecast` and `report` commands to include reasoning details in output (text or JSON)
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:11:14.839Z
Learning: Use `--live` flag with `best`, `forecast`, `report`, and `fix` commands to use live probe before decisions/output
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:11:14.839Z
Learning: Use `--dry-run` flag with `verify-flagged`, `verify`, `fix`, and `doctor` commands to preview changes without writing to storage
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:11:14.839Z
Learning: Use `--model <model>` flag with `best`, `forecast`, `report`, and `fix` commands to specify the model for live probe paths
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:11:14.839Z
Learning: Recommended workflow pack for health and planning: `codex auth check`, `codex auth forecast --live --explain --model gpt-5-codex`, `codex auth report --live --json`
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:11:14.839Z
Learning: Recommended workflow pack for repair and recovery: `codex auth fix --dry-run`, `codex auth fix --live --model gpt-5-codex`, `codex auth doctor --fix`
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:11:24.393Z
Learning: When `CODEX_MULTI_AUTH_DIR` is set, the settings file root location moves accordingly
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:11:24.393Z
Learning: Use environment variables `CODEX_MULTI_AUTH_DIR`, `CODEX_MULTI_AUTH_CONFIG_PATH`, `CODEX_MODE`, `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY`, `CODEX_MULTI_AUTH_APP_BIND_INSTALL`, `CODEX_TUI_V2`, `CODEX_TUI_COLOR_PROFILE`, `CODEX_TUI_GLYPHS`, `CODEX_AUTH_FETCH_TIMEOUT_MS`, and `CODEX_AUTH_STREAM_STALL_TIMEOUT_MS` for stable operator overrides
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:11:24.393Z
Learning: Reject named backup filenames that contain separators, traversal patterns (`..`), `.rotate.`, `.tmp`, and `.wal` suffixes
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:11:24.393Z
Learning: Always show preview before applying sync changes, and preserve destination active selection and destination-only accounts during merge preview/apply
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:11:24.393Z
Learning: After configuration changes, validate with `codex auth status`, `codex auth check`, and `codex auth forecast --live` commands
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:11:29.981Z
Learning: Maintain deterministic fallback behavior for older official Codex runtimes and non-entitled or quota-limited accounts when handling gpt-5.5 models
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:11:29.981Z
Learning: Implement `codexRuntimeRotationProxy` as opt-in feature with environment variable `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=1` and CLI commands `codex auth rotation enable|disable|status`
Learnt from: CR
Repo: ndycode/codex-multi-auth
Timestamp: 2026-04-25T03:11:29.981Z
Learning: Ensure wrapper retry path handles both ChatGPT unsupported-model errors and upstream no-access variant (`does not exist or you do not have access`) responses
🔇 Additional comments (5)
test/codex-manager-cli.test.ts (1)
9-9: good deterministic storage-health test scaffoldingthe new
inspectStorageHealthmock wiring/reset/default state makes command tests deterministic and aligned with thestoragehealthsummarycontract (test/codex-manager-cli.test.ts:9,test/codex-manager-cli.test.ts:183,test/codex-manager-cli.test.ts:661,test/codex-manager-cli.test.ts:828,lib/storage/health.ts:1).Also applies to: 183-183, 661-661, 828-836
scripts/codex.js (3)
1377-1406: bundle-level coherence on shadow-home sync-back is still per-file — concurrent sessions can publish mixed auth statethe cleanup closure now adds an "only write if
currentOriginalStatestill matches my snapshot" check (scripts/codex.js:1392-1395), which is a nice optimistic guard against trampling another session's later write. but the loop is still per-file: session A can win onauth.jsonwhile session B wins onaccounts.json, and the resulting on-disk bundle pairs anauth.jsonfrom session A with anaccounts.jsonfrom session B. that's exactly the failure mode flagged in the previous review — bundle atomicity, not file atomicity.the right shape is still: take a single bundle-level lease on
originalCodexHome(e.g., a.codex-multi-auth.sync.lockadvisory file with pid+ts), capture allSHADOW_HOME_STATE_FILESsnapshots together, decide the per-file write set, perform the renames as a group, then release. fall back to per-file best-effort only when the lease cannot be acquired within a small budget. this matches the cross-process refresh learning in the project memory.at minimum, please land a vitest regression that simulates two
createShadowHomeMirrorcleanups racing against the sameoriginalCodexHome(interleavedcaptureShadowHomeState+syncShadowHomeStateFilecalls) and asserts the final on-disk bundle is internally consistent (e.g., active account inauth.jsonexists inaccounts.json).based on retrieved learnings: "Cross-process refresh coordination must use lease/state files and should not be manually edited while the CLI is running."
1455-1469: still warning on every codex invocation whenCODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXYis malformed
parseRuntimeRotationProxyEnv(scripts/codex.js:1455-1469) is reachable fromisRuntimeRotationProxyEnabled(scripts/codex.js:1471-1492) which runs on every forwarded command. one typo in a shell rc → aconsole.errorline on everycodex …call until the user finds and fixes it. previous review asked for a module-scopedalreadyWarnedInvalidRuntimeRotationProxyguard; the code as-shown still re-warns unconditionally.+let alreadyWarnedInvalidRuntimeRotationProxy = false; + function parseRuntimeRotationProxyEnv(value) { if (value === undefined) return undefined; const normalized = value.trim().toLowerCase(); if (normalized.length === 0) return undefined; if (normalized === "1" || normalized === "true" || normalized === "yes") { return true; } if (normalized === "0" || normalized === "false" || normalized === "no") { return false; } - console.error( - "codex-multi-auth: ignoring invalid CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY value. Expected 0/1, true/false, or yes/no.", - ); + if (!alreadyWarnedInvalidRuntimeRotationProxy) { + alreadyWarnedInvalidRuntimeRotationProxy = true; + console.error( + "codex-multi-auth: ignoring invalid CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY value. Expected 0/1, true/false, or yes/no.", + ); + } return undefined; }
1498-1544: TOML table-detection regex still misses[[array_of_tables]]headersboth
removeRuntimeRotationProviderBlock(scripts/codex.js:1508) andrewriteTopLevelModelProvider(scripts/codex.js:1526) use/^\s*\[[^\]]+\]\s*$/, which does not match[[profiles.foo]]-style headers. behavioural fallout:
- in
removeRuntimeRotationProviderBlock: if our provider block is followed by a[[...]]section instead of a single-bracket table, we keep "skipping" past the array-of-tables header and silently drop user content.- in
rewriteTopLevelModelProvider: if the user'sconfig.tomlopens with a[[...]]section, we never find a "table" boundary, never injectmodel_provider = …at the top, and instead append it at end-of-file, where it gets nested under whatever[[…]]was last — codex ignores it and the proxy never activates.both regexes need to accept one or two brackets:
- if (skipping && /^\s*\[[^\]]+\]\s*$/.test(line)) { + if (skipping && /^\s*\[{1,2}[^\]]+\]{1,2}\s*$/.test(line)) { skipping = false; }- const isTable = /^\s*\[[^\]]+\]\s*$/.test(line); + const isTable = /^\s*\[{1,2}[^\]]+\]{1,2}\s*$/.test(line);still no test coverage for
rewriteTopLevelModelProvider,removeRuntimeRotationProviderBlock, orrewriteConfigTomlForRuntimeRotationProxy. please add a fixture intest/codex-bin-wrapper.test.ts(or a dedicatedtest/runtime-rotation-config-rewrite.test.ts) with aconfig.tomlthat opens with[[profiles.foo]]\nmodel = "x"\nand assertsrewriteConfigTomlForRuntimeRotationProxy(...)injectsmodel_providerat the top, above the array-of-tables header.lib/codex-manager.ts (1)
1207-1254:activeAccountMatchesCodexCliStatedefaults to "drifted" on missing identifiers andsyncCodexCliActiveSelectionIfDriftedforce-refreshes from disk on every menu iterationtwo bugs in the same helper pair at
lib/codex-manager.ts:1208-1254, both live in the menu hot path:
activeAccountMatchesCodexCliStatereturnsfalsewhenever neither accountId pair nor email pair can be compared — including cases where the local account has no accountId but the cli state does. that flips into a "drifted" condition and writesundefinedids back into cli state viasetCodexCliActiveSelection, clobbering valid state. fail-safe direction is "no drift" when we cannot compare. changereturn falsetoreturn trueat line 1224.
syncCodexCliActiveSelectionIfDriftedcallsloadCodexCliState({ forceRefresh: true })at line 1233 on every menu render (line 2789 is inside thepromptLoginModeloop). this bypasses in-process cache and hits disk every iteration, stacking on top ofloadFlaggedAccounts()+loadDashboardDisplaySettings()already running per pass. gate the call to only run once per 5 seconds or similar.no vitest coverage exists for either helper — land regression tests covering (a) account with only email vs cli state with only accountId → expect no write, and (b) drift detected → single write per drift cycle, not per render. check
test/codex-manager*.test.tsor whereverrunAuthLoginis exercised.
Summary
codex app, andcodex app-serverlaunches through the runtime rotation path while preserving the real Codex home via shadow-home mirroring and sync-back.codex auth rotation enable|disable|statusmanagement commands backed by shared runtime constants.Review Cleanup
content-encodingbefore forwarding decoded Nodefetchbodies to local Codex clients.process.stdinduring app-server protocol-proxy cleanup so retry attempts cannot inherit a paused stdin stream after child backpressure.Regression Coverage
test/runtime-rotation-proxy.test.tscovers decoded upstream response forwarding without stale compression headers.test/codex-bin-wrapper.test.tscovers transient and persistent shadow sync lock owner-write failures.test/codex-bin-wrapper.test.tsnow asserts app-server protocol cleanup resumesprocess.stdinafter listener teardown.Validation
git diff --checknpm.cmd test -- test/runtime-rotation-proxy.test.ts test/codex-bin-wrapper.test.ts(101 tests)npm.cmd test -- test/codex-bin-wrapper.test.ts(80 tests)npm.cmd run typechecknpm.cmd run lintnote: greptile review for oc-chatgpt-multi-auth. cite files like
lib/foo.ts:123. confirm regression tests + windows concurrency/token redaction coverage.Greptile Summary
this PR wires a loopback-only responses-api rotation proxy into codex CLI and app sessions, adds shadow-home mirroring with per-file compare-and-swap sync-back, app-bind/router startup files for macOS and Windows, a protocol proxy that rewrites
account/readresponses for app-server sessions, and thecodex auth rotationmanagement commands. it also consolidates runtime constants and centralises TOML rewriting intolib/runtime/config-toml.ts.the previous greptile review cleanup landed several substantive fixes:
acquireShadowHomeSyncLocknow rolls back the lock dir on owner-write failure;removeStaleShadowHomeSyncLockhandles a nullownerPidwith an age gate; the orphan-lock stale-age constant was raised to 2 s;process.stdin.resume()is called incleanupProtocolProxy; the shim dir is cleaned in the helper'scleanup(); Windows.cmd%escaping is implemented;closeServernow destroys all tracked sockets; and the cold-start ephemeral-port bypass is blocked incodex-app-router.js.Confidence Score: 4/5
safe to merge; the prior critical auth-bypass and race-condition issues are resolved, leaving only moderate quality concerns
the previously flagged P0/P1 issues (proxy auth bypass, orphaned lock dir, stdin.resume missing, shim accumulation, duplicate constant, Windows % escaping) are all addressed. the remaining findings are P2: stopRouter's missing SIGKILL escalation, a macOS launchd KeepAlive restart race on unbind, and the temp-file name collision in syncShadowHomeStateFile. none of these block correctness for the vast majority of sessions.
lib/runtime/app-bind.ts (stopRouter SIGKILL gap and macOS launchd KeepAlive race), scripts/codex.js (syncShadowHomeStateFile temp-name collision)
Important Files Changed
stopRouterlacks SIGKILL escalation and the macOS plist hasKeepAlive=truewithout alaunchctl bootouton unbind, creating a brief launchd restart raceSequence Diagram
sequenceDiagram participant Wrapper as codex.js wrapper participant Proxy as RuntimeRotationProxy participant Shadow as ShadowHome participant Router as codex-app-router participant CLI as codex CLI participant Upstream as OpenAI API Note over Wrapper: codex exec/review/fork Wrapper->>Proxy: startRuntimeRotationProxy({clientApiKey}) Proxy-->>Wrapper: {baseUrl, close, getStatus} Wrapper->>Shadow: createRuntimeRotationProxyCodexHome() Shadow-->>Wrapper: {env CODEX_HOME=shadow, cleanup()} Wrapper->>CLI: spawn with shadow home + proxy config.toml CLI->>Proxy: POST /backend/api/v1/responses Bearer clientApiKey Proxy->>Proxy: timingSafeEqual auth check Proxy->>Upstream: POST with real Bearer token Upstream-->>Proxy: streaming SSE Proxy-->>CLI: forward (socket.destroy on shutdown) Note over Wrapper: codex app (helper path) Wrapper->>Router: spawn app-helper detached Router->>Proxy: startRuntimeRotationProxy Router->>Shadow: createRuntimeRotationProxyCodexHome Router-->>Wrapper: {type:ready, baseUrl, env, pid} Wrapper->>CLI: spawn codex app with helperEnv Note over Shadow: on session exit Shadow->>Shadow: acquireShadowHomeSyncLock Shadow->>Shadow: syncShadowHomeAuthBundle (CAS + rebase) Shadow->>Shadow: releaseLock + rmdir shadowCodexHomePrompt To Fix All With AI
Reviews (34): Last reviewed commit: "Resume stdin after app protocol cleanup" | Re-trigger Greptile