Skip to content

fix(codex): route app-server through the canonical home (#659) - #660

Closed
possibilities wants to merge 5 commits into
ndycode:mainfrom
possibilities:fix/app-server-canonical-home
Closed

fix(codex): route app-server through the canonical home (#659)#660
possibilities wants to merge 5 commits into
ndycode:mainfrom
possibilities:fix/app-server-canonical-home

Conversation

@possibilities

@possibilities possibilities commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

What Changed

scripts/codex.js

  • createRuntimeRotationProxyContextIfEnabled gets an app-server branch above the interactive one, using the canonical-home app helper. Two deliberate differences from that branch:
    • detachOnExit: false — a resident server owns its proxy for its whole lifetime. Leaving it to idle out would strand one helper per supervised restart.
    • proxyAppServerAccountRead: true, threaded through createRuntimeRotationAppHelperContext's options. Only the shadow branch used to set this, so without it the transport move would silently drop the account/read / getAuthStatus / account/rateLimits/read rewriting that stdio clients already rely on. when is unchanged — the gate still requires isCodexAppServerCommand, and the CODEX_MULTI_AUTH_APP_SERVER_ACCOUNT_LABEL path is untouched.
  • The detach grace now applies only when the caller has not decided for itself. Previously cleanup detached on any clean exit inside the 5s window regardless of detachOnExit: false, so a short-lived app-server — a client attaches, runs one query, disconnects — still stranded its helper for the full 12h idle timeout with nobody left to refresh the activity clock. detachOnExit === true detaches past the window; an unset detachOnExit still detaches inside it; an explicit false never does. The TUI passes true and codex app passes neither, so neither changes behavior.

test/codex-bin-wrapper.test.ts

  • New test: app-server --listen unix://… runs on the canonical home, sees the real thread index, gets a real app-server-control directory, keeps the rotation overrides, and leaves config.toml untouched.
  • The existing stdio app-server test now asserts the canonical shape (provider via -c overrides with env_key, canonical CODEX_HOME, config.toml byte-identical) instead of the shadow shape. Its intent is unchanged; only the transport it pins moved.
  • Neither test shortens CODEX_MULTI_AUTH_APP_ROTATION_IDLE_MS, so both fail against the old grace clause and pass against the new one — the stranding fix is regression-tested rather than asserted.

Docsdocs/development/ARCHITECTURE.md, docs/architecture.md, docs/reference/commands.md, docs/configuration.md. The last three each stated that app-server runs on a shadow home and would otherwise have shipped false.

Validation

  • npm run lint
  • npm run typecheck
  • npm test
  • npm test -- test/documentation.test.ts — 32/32
  • npm run build

npm test has pre-existing failures on macOS unrelated to this change — Windows-path tests in test/codex-bin-wrapper.test.ts, test/codex-bin-resolver, and test/paths.test.ts, plus named-backup tests in test/storage.test.ts and test/install-codex-auth.test.ts. I compared matched worktrees (upstream 7f5c61b vs this branch, same node_modules) and the failure sets are the same modulo run-to-run variation under load; the composition shifts between runs even on an unchanged tree, and nothing in this diff touches those paths. test/codex-bin-wrapper.test.ts app-server tests: 11/11.

Also verified against real accounts on codex-cli 0.147.0: the pinned account governs the server's model calls, read back from each turn's rollout rate_limits against two accounts with distinguishable quota windows, and rollouts land in the canonical sessions directory.

Docs and Governance Checklist

  • README updated (if user-visible behavior changed) — n/a, README does not describe the transport
  • docs/getting-started.md updated — n/a, onboarding unchanged
  • docs/features.md reviewed — no transport claims
  • relevant docs/reference/* pages updated — commands.md
  • docs/upgrade.md updated — n/a, no migration behavior
  • SECURITY.md and CONTRIBUTING.md reviewed for alignment — this is a transport-selection fix; it adds no auth surface, no scraping, no rate-limit circumvention

Risk and Rollback

Risk level: low-medium. Behavior changes for one command, in the direction the same reasoning already took two others.

Two consequences worth naming rather than burying:

  1. Degradation differs. The shadow path falls back to rotation-off when the proxy cannot start; the helper branches propagate the failure and the wrapper exits. So a proxy that fails to start now fails an app-server hard instead of silently running it unrotated. I believe hard-fail is the right behavior for this surface — a resident server that quietly loses rotation serves every attached client on whatever account the official CLI resolves, which is a silent billing error rather than a visible one, and it matches how --account already fails hard. Happy to add a fallback if you would rather preserve the old shape.
  2. -c model_provider precedence. As with the TUI branch, the wrapper's override is appended last and now wins over a user-supplied one; on the shadow path a user value beat the shadow config.toml. Inherited from the accepted branch rather than introduced here.

Rollback: revert the commits; nothing persists and no state migrates. The only on-disk difference while running is that app-server no longer creates a shadow home under runtime-shadow-homes/.

Additional Notes

Found while building a multi-account balancer that runs one account-pinned app-server per account. Independently reviewed before submission; the detach-grace defect above came out of that review rather than out of the original patch.

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

Greptile Summary

routes codex app-server through the canonical codex home while retaining runtime rotation and account-response rewriting.

  • keeps the canonical thread index and real app-server-control directory available.
  • ties helper cleanup to the resident server lifetime.
  • updates vitest coverage and architecture, configuration, command, and upgrade documentation.

Confidence Score: 5/5

the pr appears safe to merge.

no blocking failure remains.

Important Files Changed

Filename Overview
scripts/codex.js adds canonical-home app-server routing, preserves account rewriting, and explicitly prevents helper detachment.
test/codex-bin-wrapper.test.ts replaces the unreliable windows close-marker assertion with helper liveness polling and covers canonical-home routing.
docs/development/ARCHITECTURE.md documents app-server transport selection and helper lifecycle behavior.
docs/configuration.md documents canonical-home app-server routing and transport-free help and schema commands.

Sequence Diagram

sequenceDiagram
  participant Client
  participant Wrapper
  participant Helper
  participant Server as codex app-server
  Client->>Wrapper: launch app-server
  Wrapper->>Helper: start rotation proxy
  Wrapper->>Server: canonical CODEX_HOME + provider overrides
  Server-->>Client: canonical thread index
  Server-->>Wrapper: exit
  Wrapper->>Helper: stop helper
Loading

Reviews (3): Last reviewed commit: "docs: cover the app-server help flags an..." | Re-trigger Greptile

Context used:

possibilities and others added 3 commits August 10, 2026 11:56
`codex-multi-auth-codex app-server` fell through to the shadow-home
transport, and a resident server cannot live there.

It usually cannot even start. Codex refuses when
`<CODEX_HOME>/app-server-control` exists and is not a directory, and the
shadow mirror symlinks that directory like any other, so on any home that
has already run an app-server the child exits immediately with
`socket directory path exists and is not a directory`. A server that does
start is worse off than one that fails: the mirror snapshots the runtime
SQLite state rather than linking it, so every attached client drives
threads against a frozen, throwaway index — the same divergence that hung
`resume` on a blank screen (ndycode#647), except an app-server holds it for the
whole life of the process and hands it to every client that attaches.

Classify `app-server` with the predicate that already exists for it and
route it to the canonical-home app helper, alongside the interactive entry
points. Two details differ from that branch:

- `detachOnExit: false`. A resident server owns its proxy for its whole
  lifetime, so the helper stops when the server does. Leaving it to idle
  out would strand one proxy helper per supervised restart.
- `proxyAppServerAccountRead` is threaded through the helper context and
  set for this branch, so moving the transport does not silently drop the
  `account/read` / `getAuthStatus` / `account/rateLimits/read` rewriting
  that stdio clients already rely on. Only the shadow branch used to set
  it.

Verified end to end against codex-cli 0.147.0 with two managed accounts:
the pinned account governs the server's model calls, proved by reading
each turn's rollout `rate_limits` back against the accounts' distinct
quota windows, and rollouts land in the canonical `sessions` directory.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0134wwvqFQA87kf1K2cLeEk6
`docs/reference/commands.md` and `docs/configuration.md` both stated that
`codex app-server` runs against a shadow `CODEX_HOME`. It now stays on the
canonical home like the interactive TUI, and for app-server that placement
is required rather than an optimization, so both pages say why.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0134wwvqFQA87kf1K2cLeEk6
Review catch. `detachOnExit: false` was not actually honored for a
short-lived server: the grace clause detaches on any clean exit inside
the window, so an app-server that a client attaches to, queries once, and
disconnects from within five seconds still left its helper and proxy
running for the full 12h idle timeout — with the owner gone and nothing
refreshing the activity clock. That is precisely the stranding the branch
claims to prevent.

The window exists for callers that hand off and return immediately;
`codex app` is the real one. So it now applies only when the caller has
not decided for itself: `detachOnExit === true` detaches past the window,
an unset `detachOnExit` still detaches inside it, and an explicit `false`
never detaches. TUI passes true and `codex app` passes neither, so both
keep their behavior exactly.

Both app-server tests drop the shortened idle timeout they needed to see
the proxy close, which turns each of them into a regression test for
this: they fail against the old clause and pass against the new one.

Also restores the `cli_auth_credentials_store="file"` assertion the
earlier test rewrite dropped for app-server, and corrects
docs/architecture.md, which still described the canonical home as
interactive-only and labelled the sole runtime path as shadow.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0134wwvqFQA87kf1K2cLeEk6
@possibilities
possibilities requested a review from ndycode as a code owner August 10, 2026 16:56
@chatgpt-codex-connector

Copy link
Copy Markdown

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

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

severity: minor. the change routes codex app-server through canonical CODEX_HOME and preserves provider overrides, account response rewriting, and server-lifetime proxy attachment (scripts/codex.js:line). this prevents stale thread indexes and control-directory startup failures. regression tests cover canonical state, control directories, rotation overrides, unchanged configuration, and platform-independent helper shutdown (test/codex-bin-wrapper.test.ts:line).

reviewers should focus on the architectural choice to set detachOnExit: false for resident app-server processes while retaining automatic cleanup for short-lived commands (scripts/codex.js:line). no security or data-loss risk is identified. windows path behavior and concurrent server shutdown and proxy rotation remain explicit edge cases to verify. app-server-specific cleanup-retry coverage is not included.

  • updated architecture, command, and configuration documentation, including help and schema-generation transport behavior (docs/architecture.md:line, docs/development/ARCHITECTURE.md:line, docs/reference/commands.md:line, docs/configuration.md:line)
  • updated app-server routing, canonical-home handling, response rewriting, and helper lifecycle behavior (scripts/codex.js:line)
  • expanded app-server wrapper regression coverage, including helper-process liveness checks (test/codex-bin-wrapper.test.ts:line)
  • validation includes lint, typecheck, build, documentation tests, and app-server wrapper tests; three remaining wrapper failures are pre-existing windows-path failures

Walkthrough

app-server runtime rotation now uses canonical CODEX_HOME, provider -c overrides, account-read proxying, and helper shutdown tied to the resident server. tests validate routing, state preservation, control directories, thread indexing, listen forwarding, and asynchronous cleanup.

Changes

app-server runtime rotation

Layer / File(s) Summary
canonical-home routing and helper lifecycle
scripts/codex.js:4289-4299, scripts/codex.js:4334, scripts/codex.js:4365-4374, scripts/codex.js:4574-4581
explicit detachOnExit: false keeps the helper attached to the app-server lifetime. app-server uses canonical-home state and account-read proxying.
app-server routing regression coverage
test/codex-bin-wrapper.test.ts:2149, test/codex-bin-wrapper.test.ts:2177-2224, test/codex-bin-wrapper.test.ts:3185-3271
tests verify canonical-home routing, provider overrides, account labeling, unchanged configuration, control-directory handling, thread indexing, listen forwarding, and asynchronous shutdown. missing coverage remains for windows-specific behavior and concurrent-client lifecycle handling.
runtime transport documentation
docs/architecture.md:30, docs/architecture.md:63-64, docs/architecture.md:205-206, docs/configuration.md:143, docs/development/ARCHITECTURE.md:58-63, docs/development/ARCHITECTURE.md:72-75, docs/development/ARCHITECTURE.md:171-178, docs/development/ARCHITECTURE.md:193-194, docs/reference/commands.md:550
documentation distinguishes shadow-home request commands from canonical-home interactive, resume/fork, and app-server sessions. it documents app-server control-directory, proxy-bypass, and helper-lifetime constraints.

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

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant AppServer
  participant RuntimeAppHelper
  participant CanonicalCodexHome
  Client->>AppServer: connect to the requested listen address
  AppServer->>RuntimeAppHelper: start with provider overrides
  RuntimeAppHelper->>CanonicalCodexHome: read and update canonical thread state
  RuntimeAppHelper-->>AppServer: proxy provider and account-read traffic
  AppServer-->>Client: serve sessions and threads
  AppServer->>RuntimeAppHelper: signal resident-server exit
  RuntimeAppHelper->>RuntimeAppHelper: terminate asynchronously
Loading

Possibly related PRs

Suggested reviewers: ndycode

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed the implementation satisfies issue #659 by using canonical CODEX_HOME, preserving provider rewriting, and testing the behavior in test/codex-bin-wrapper.test.ts:2209.
Out of Scope Changes check ✅ Passed the changes remain within issue #659: routing, helper lifetime, regression tests, and related documentation; no unrelated behavior is changed in test/codex-bin-wrapper.test.ts:3214.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed the title follows conventional commits and accurately summarizes the canonical-home app-server routing change.
Description check ✅ Passed the description includes the required sections, validation results, documentation updates, risks, rollback plan, and regression coverage in test/codex-bin-wrapper.test.ts.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified 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.

❤️ Share

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

Comment thread test/codex-bin-wrapper.test.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

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

Inline comments:
In `@docs/configuration.md`:
- Line 143: The app-server routing documentation must apply only to resident
app-server runs and explicitly note that app-server --help, help, generate-ts,
and generate-json-schema bypass the proxy. Update the paragraph in
docs/configuration.md lines 143-143 and the corresponding command-reference text
in docs/reference/commands.md lines 550-550; qualify both the proxy and -c
override statements consistently, with no direct changes required elsewhere.

In `@docs/development/ARCHITECTURE.md`:
- Line 172: Update the architecture documentation’s transport count from three
to four and revise the system diagram to include the canonical-home app-server
branch identified by isCodexAppServerCommand, matching the routing contract in
scripts/codex.js. Ensure the diagram shows app-server following its resident
proxy ownership path rather than the legacy shadow-home path.
- Around line 187-188: Extend test/codex-bin-wrapper.test.ts with regression
coverage for app-server lifecycle: verify the app-server helper shuts down after
the server exits, and add Windows-specific coverage that exercises cleanup retry
behavior during the app-server process-exit flow. Reuse the existing canonical
CODEX_HOME, concurrent helper ownership, stale stdio shutdown, and shadow
cleanup test patterns where applicable, while asserting app-server-specific
process exit and cleanup behavior.
🪄 Autofix

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 Plus

Run ID: 4eaa7314-7c42-4d7a-8108-713db27a2612

📥 Commits

Reviewing files that changed from the base of the PR and between 7f5c61b and 79f492d.

📒 Files selected for processing (6)
  • docs/architecture.md
  • docs/configuration.md
  • docs/development/ARCHITECTURE.md
  • docs/reference/commands.md
  • scripts/codex.js
  • test/codex-bin-wrapper.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (17)
docs/**/*.md

📄 CodeRabbit inference engine (docs/STYLE_GUIDE.md)

docs/**/*.md: User-facing documentation should follow the page template: Title and one-line lead, Quick path commands, Core operational workflow, Troubleshooting or failure handling, and Related links
Use short sections and scan-friendly tables in documentation where they improve clarity
Prefer direct, actionable language in documentation
Use runnable command examples in documentation
Explain expected outcomes after critical commands in documentation
Keep terminology consistent with runtime names in documentation
Avoid speculative language when behavior is deterministic in documentation
Put the user problem in the first paragraph before implementation detail
Use descriptive page titles such as codex-multi-auth Features instead of generic titles on public docs
Do not repeat keyword lists in every section; search terms should appear only where they help a developer understand the page
Canonical command family is codex-multi-auth ...
Canonical runtime root is ~/.codex/multi-auth
Runtime rotation must be described as default-on unless the release policy changes
Legacy command/path references belong only in migration contexts in documentation
Compatibility aliases (codex multi auth, codex multi-auth, codex multiauth) belong only in command reference, troubleshooting, or migration contexts
Keep command flags aligned with runtime usage text in documentation
Avoid non-runnable command snippets in documentation
Avoid conflicting path guidance across documentation
Avoid legacy-first onboarding language in documentation

Organize repository documentation according to the defined layers: product entry, user operations, reference, and development.

docs/**/*.md: Do not describe codex-multi-auth as replacing @openai/codex or publishing the global codex binary; preserve the official CLI's ownership of codex.
Use codex-multi-auth for account management, and reserve codex-multi-auth-codex or mcodex for intentionally forwarding official Codex commands th...

Files:

  • docs/configuration.md
  • docs/architecture.md
  • docs/development/ARCHITECTURE.md
  • docs/reference/commands.md
docs/{index.md,getting-started.md,faq.md,architecture.md,features.md,configuration.md,troubleshooting.md,privacy.md,upgrade.md}

📄 CodeRabbit inference engine (docs/DOCUMENTATION.md)

Keep the listed public documentation pages as the canonical sources for operator onboarding, FAQ, architecture, features, configuration, troubleshooting, privacy, and upgrades.

Files:

  • docs/configuration.md
  • docs/architecture.md
docs/**/*.{md,mdx}

📄 CodeRabbit inference engine (docs/troubleshooting.md)

Document that codex-multi-auth-codex is the optional forwarding wrapper, while codex-multi-auth is the canonical account-manager command family; the package does not publish a global codex binary.

Document the canonical command names, runtime paths, configuration precedence, storage migration behavior, and upgrade procedures consistently across the referenced documentation.

Files:

  • docs/configuration.md
  • docs/architecture.md
  • docs/development/ARCHITECTURE.md
  • docs/reference/commands.md
**/*

📄 CodeRabbit inference engine (AGENTS.md)

Source changes belong in index.ts, lib/, and scripts/; dist/ is generated output and local temporary/cache directories must not be edited.

Files:

  • docs/configuration.md
  • docs/architecture.md
  • test/codex-bin-wrapper.test.ts
  • docs/development/ARCHITECTURE.md
  • docs/reference/commands.md
  • scripts/codex.js
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/configuration.md
  • docs/architecture.md
  • docs/development/ARCHITECTURE.md
  • docs/reference/commands.md
test/**/*.test.ts

📄 CodeRabbit inference engine (test/AGENTS.md)

test/**/*.test.ts: Write Vitest test suites with globals enabled (describe, it, expect)
Maintain 80%+ coverage threshold across statements, branches, functions, and lines
Use removeWithRetry() for Windows filesystem cleanup instead of bare fs.rm to handle EBUSY, EPERM, and ENOTEMPTY errors
Do not rely on dist/ in tests; use source files instead
Do not skip tests without justification
Relax lint rules for test files as configured in eslint.config.js

Files:

  • test/codex-bin-wrapper.test.ts
test/**/codex-bin-wrapper.test.ts

📄 CodeRabbit inference engine (test/AGENTS.md)

Test bin wrapper lazy-load and missing dist handling with concurrent invocations in codex-bin-wrapper.test.ts

Files:

  • test/codex-bin-wrapper.test.ts
**/*.{ts,js,mjs}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,js,mjs}: Use ESM modules throughout the project; the package is configured with "type": "module".
Do not use as any, @ts-ignore, or @ts-expect-error.

Files:

  • test/codex-bin-wrapper.test.ts
  • scripts/codex.js
test/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Windows-sensitive filesystem tests and helpers must use retry handling for transient lock-related cleanup and write failures.

Files:

  • test/codex-bin-wrapper.test.ts
**/*.{js,ts,mjs,cjs}

📄 CodeRabbit inference engine (README.md)

**/*.{js,ts,mjs,cjs}: Do not publish or replace a global codex binary; official OpenAI installation paths must retain ownership of the codex command.
Keep OAuth credentials local and restrict runtime rotation and local bridges to loopback interfaces.
Require hashed local client tokens to protect the optional loopback bridge.
Responses background: true compatibility must remain opt-in; requests using it must use stateful store=true routing rather than stateless store=false routing.
Never run npm install or update commands automatically; only display a manual upgrade notice when appropriate.
Experimental synchronization and backup flows must be non-destructive by default: preview before applying sync, preserve destination-only accounts, and fail safely on backup filename collisions.
Keep account storage project-scoped under the configured multi-auth root when operating in repo-specific workflows.

Files:

  • test/codex-bin-wrapper.test.ts
  • scripts/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/codex-bin-wrapper.test.ts
docs/development/**/*.md

📄 CodeRabbit inference engine (docs/DOCUMENTATION.md)

Keep internal architecture, configuration flow, repository ownership, testing, parity, metadata, and audit guidance in development documentation.

Prefer current architecture and reference documentation over historical plans and audit snapshots when describing the present system.

Files:

  • docs/development/ARCHITECTURE.md
docs/development/**/*

📄 CodeRabbit inference engine (docs/development/CONFIG_FLOW.md)

docs/development/**/*: Resolve the runtime root directory in this order: CODEX_MULTI_AUTH_DIR; explicit non-default CODEX_HOME/multi-auth; existing account-storage roots under CODEX_HOME or ~/.codex; canonical ~/.codex/multi-auth; and legacy paths only when storage signals exist.
Read dashboardDisplaySettings and pluginConfig from settings.json, while preserving legacy compatibility loading and migration.
Resolve pluginConfig values using this precedence: existing CODEX_MULTI_AUTH_CONFIG_PATH file, valid unified settings.json configuration, legacy compatibility configuration, then DEFAULT_PLUGIN_CONFIG; apply environment-variable overrides afterward.
Ignore a configured but nonexistent CODEX_MULTI_AUTH_CONFIG_PATH during loading, but create it on the first save while the variable remains set.
Resolve dashboard display values from persisted dashboardDisplaySettings, followed by normalization and fallback defaults.
Resolve account storage by selecting the root directory, using the global accounts file by default, using a project-namespaced path when project-scoped mode is active, and attempting applicable legacy project-file migration.
Normalize standalone codex-multi-auth bare subcommands to auth ... before dispatch; normalize wrapper aliases; run auth-manager commands locally; forward out-of-scope wrapper commands to the official Codex CLI.
For forwarded request-bearing commands, honor runtime rotation: resolve CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY, then pluginConfig.codexRuntimeRotationProxy, which defaults to enabled.
When rotation is enabled for a requesting command, use a per-process-token loopback Responses proxy, a temporary shadow CODEX_HOME, and a rewritten config.toml; synchronize refreshed official Codex state on exit and remove the shadow home.
The runtime proxy must select or refresh managed accounts and rotate on rate-limit, authentication, network, or server failures before streaming begins.
The plugin host m...

Files:

  • docs/development/ARCHITECTURE.md
docs/development/**/*.{md,mdx}

📄 CodeRabbit inference engine (docs/development/TESTING.md)

When documentation changes, verify every command snippet is runnable, path references match runtime modules, cross-links are valid, and the feature matrix matches implemented features.

Files:

  • docs/development/ARCHITECTURE.md
docs/reference/**/*.md

📄 CodeRabbit inference engine (docs/STYLE_GUIDE.md)

New flags/settings/paths must be reflected in docs/reference/*

docs/reference/**/*.md: Keep command, API, error-contract, settings, and storage-path details in the canonical reference documentation.
Document compatibility aliases (codex multi auth, codex multi-auth, and codex multiauth) only in command-reference, troubleshooting, or migration sections.

Files:

  • docs/reference/commands.md
scripts/codex*.js

📄 CodeRabbit inference engine (AGENTS.md)

The wrapper must not reimplement general Codex commands; authentication commands are handled locally and non-authentication commands must forward to the official Codex CLI.

Files:

  • scripts/codex.js
scripts/**/*.js

📄 CodeRabbit inference engine (AGENTS.md)

Windows-sensitive cleanup and write operations must retry transient EBUSY, EPERM, and ENOTEMPTY failures where applicable.

Files:

  • scripts/codex.js
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T16:57:49.777Z
Learning: Ignore a set-but-missing `CODEX_MULTI_AUTH_CONFIG_PATH` during load, but use it as the save target when the variable is set.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T16:57:49.777Z
Learning: When `CODEX_HOME` is non-default, resolve multi-auth strictly under `$CODEX_HOME/multi-auth` without scanning other roots for existing pools.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T16:57:49.777Z
Learning: `CODEX_MULTI_AUTH_DIR` must re-home all multi-auth-owned settings, accounts, cache, and log directories.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T16:57:49.777Z
Learning: A per-invocation account selected by `--account` or `CODEX_MULTI_AUTH_FORCE_ACCOUNT` must be ephemeral, fail hard when the rotation proxy is disabled, and never modify or leak the persisted account-switch pin.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T16:57:49.777Z
Learning: The runtime rotation proxy must preserve request bodies and streaming responses, replace outbound authentication headers, and remove hop-by-hop, private account metadata, and stale decoded `content-encoding` headers from responses.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T16:57:49.777Z
Learning: When all accounts are unavailable, return a structured pool-exhaustion error that directs users to `codex-multi-auth rotation status` rather than silently failing over indefinitely.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T16:57:49.777Z
Learning: Explicit OAuth token revocation responses must be returned directly instead of rotating accounts; the affected account must receive the configured token-invalidation cooldown.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T16:57:49.777Z
Learning: Package installation scripts must remain side-effect-free; postinstall may print only a short notice and must not modify user configuration or install updates.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T16:57:49.777Z
Learning: First-run self-healing must run only on durable global installs, not `npx` or project-local installs, and must record completion at `~/.codex/multi-auth/first-run-setup.json`.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T16:57:49.777Z
Learning: Automatic npm version checks may only print a manual update notice; wrappers must never execute npm install or update commands, and notices should be limited to a TTY or debug mode.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T16:57:49.777Z
Learning: The official Codex CLI credential store must be persisted as `cli_auth_credentials_store = "file"` unless `CODEX_MULTI_AUTH_ENFORCE_CLI_FILE_AUTH_STORE=0` opts out.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T16:57:49.777Z
Learning: Deprecated selectors such as `gpt-5-codex` and `gpt-5.1-codex*` must be treated as compatibility aliases and retried with the current documented Codex model only after the Codex surface returns a genuine unsupported-model response.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T16:58:02.778Z
Learning: The runtime rotation proxy is default-on, local, reversible, and loopback-only; explicit opt-out controls must remain available.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T16:58:02.778Z
Learning: Budget guards are soft under concurrency: evaluations use a pre-request ledger snapshot, and brief concurrent overshoot is intentional rather than treated as a hard distributed quota.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T16:58:02.778Z
Learning: The canonical account-management command surface remains `codex-multi-auth ...`, while the Codex wrapper preserves official CLI behavior for unrelated commands.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T16:58:11.134Z
Learning: Treat documented public API and command behavior as compatibility contracts; additive JSON surfaces must not invalidate the documented anchor list.
📚 Learning: 2026-06-04T06:14:18.093Z
Learnt from: ndycode
Repo: ndycode/codex-multi-auth PR: 510
File: test/scheduling-strategy-config.test.ts:1-1
Timestamp: 2026-06-04T06:14:18.093Z
Learning: In ndycode/codex-multi-auth, do not flag explicit imports from "vitest" (e.g., describe, it, expect, beforeEach/afterEach, etc.) in test files as issues—even if the Vitest config sets `globals: true`. The repo’s established convention is to keep these imports for consistency with neighboring tests; removing them would make files outliers.

Applied to files:

  • test/codex-bin-wrapper.test.ts
📚 Learning: 2026-06-04T06:14:24.975Z
Learnt from: ndycode
Repo: ndycode/codex-multi-auth PR: 510
File: test/runtime-rotation-proxy.test.ts:2478-2491
Timestamp: 2026-06-04T06:14:24.975Z
Learning: In ndycode/codex-multi-auth test files (e.g. `test/*.test.ts`), when creating V3 storage fixtures for accounts, it’s an intentional convention to use `as never` for deliberately minimal stored-account objects that only include `refreshToken`, `addedAt`, and `lastUsed`. Do not treat `as never` here as a type-safety problem: optional/other fields are expected to be populated by the runtime during execution, and the cast is used solely to keep the fixture minimal and consistent across existing tests.

Applied to files:

  • test/codex-bin-wrapper.test.ts
🪛 ast-grep (0.45.0)
test/codex-bin-wrapper.test.ts

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { type SpawnSyncReturns, spawn, spawnSync } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { type SpawnSyncReturns, spawn, spawnSync } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { type SpawnSyncReturns, spawn, spawnSync } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🔇 Additional comments (4)
scripts/codex.js (1)

4289-4299: LGTM!

Also applies to: 4334-4334, 4365-4374, 4574-4581

test/codex-bin-wrapper.test.ts (1)

2149-2149: LGTM!

Also applies to: 2182-2209, 3170-3241

docs/architecture.md (1)

30-30: LGTM!

Also applies to: 63-64, 205-206

docs/reference/commands.md (1)

550-550: 📐 Maintainability & Code Quality

verify the required upgrade note.

This change moves codex app-server from the shadow home to the canonical CODEX_HOME. Check docs/upgrade.md and add the migration note. Mention any new npm scripts, or state that none were added.

As per path instructions, behavior changes require updated upgrade notes and mention of new npm scripts.

Source: Path instructions

Comment thread docs/configuration.md Outdated
| Interactive TUI | `isCodexInteractiveTuiCommand` — no forwarded subcommand at all | App runtime helper with `useCanonicalHome: true` and `detachOnExit: true`. Runs against the **canonical** `CODEX_HOME`; the provider is passed as ephemeral `-c model_providers.*` overrides. No shadow copy and no state sync-back. Nothing **provider- or transport-related** is written into `config.toml` on this path — the only top-level key the wrapper still reconciles there is `cli_auth_credentials_store`, which is transport-independent (see step 4 note). |
| Interactive `resume` / `fork` | `isCodexInteractiveResumeCommand` — forwarded command is `resume` or `fork` | Same transport and options as the interactive TUI above. These open a TUI against an existing thread, so they must see the canonical thread index. |
| `codex app` | `isCodexAppCommand` — forwarded command is `app` | App runtime helper process with a shadow `CODEX_HOME`. |
| `app-server` | `isCodexAppServerCommand` — forwarded command is `app-server` | Canonical-home app helper, but `detachOnExit: false`: a resident server owns its proxy for its whole lifetime. `account/read` rewriting stays on. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

update the transport count and system diagram.

This branch adds a fourth runtime transport, but line 165 still says “one of three transports,” and the system diagram omits app-server. Readers can follow the old shadow-home path for this command. Add the canonical-home app-server branch to the diagram and change the count to four. scripts/codex.js:4574-4660 is the routing contract.

As per path instructions, development documentation must prefer the current architecture and verify feature matrices against implemented features.

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

In `@docs/development/ARCHITECTURE.md` at line 172, Update the architecture
documentation’s transport count from three to four and revise the system diagram
to include the canonical-home app-server branch identified by
isCodexAppServerCommand, matching the routing contract in scripts/codex.js.
Ensure the diagram shows app-server following its resident proxy ownership path
rather than the legacy shadow-home path.

Source: Path instructions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Keeping three, and flagging this as the one open disagreement on the PR so a human can settle it.

The count was already mechanism-based before this branch: the same sentence said "one of three transports" over four branch rows — TUI, resume/fork, app, everything else. So rows and transports were never 1:1 here, and reading a new row as a new transport contradicts the file's own established usage.

app-server reuses the canonical-home app helper the interactive commands already use. detachOnExit is lifecycle and proxyAppServerAccountRead is a wrapper-side protocol rewrite; neither changes home placement nor how traffic reaches the proxy. The three mechanisms are unchanged: canonical-home helper, shadow-home helper for codex app, inline shadow home for everything else.

What was genuinely wrong is that the convention was implicit, so the sentence now states it ("more branches than transports"), and the diagram — which was stale in a way that predates this PR, missing resume/fork since #648 — now lists every branch under the transport it takes, including app-server and the no-transport help path.

@ndycode if you count rows rather than mechanisms in this document, say so and I'll change it to four in one commit.

Comment thread docs/development/ARCHITECTURE.md
Review round 2.

The app-server tests waited for the proxy's `close` marker to prove the
helper stopped rather than detached. That only works where the helper
gets to run its SIGTERM handler: on Windows the terminate is
unconditional, so the marker never arrives even though cleanup did
exactly what it should, and the assertion would hang for five seconds
before failing. Both tests now read the helper's pid from the marker and
poll until the process is gone, which is true on every platform. They
still fail against the previous detach-grace clause, so the regression
they were added for is intact.

Docs: the runtime-rotation transport diagram listed three branches and
had been missing `resume`/`fork` since they moved to the canonical home;
it now lists every branch under the transport it takes, including
app-server, and records that help and schema generation take no
transport at all. `docs/reference/commands.md` and `docs/configuration.md`
say the same, so neither reads as though `app-server --help` starts a
proxy — while still noting that the transport skip does not stop the
transport-independent `cli_auth_credentials_store` reconcile, which
`buildForwardArgs` appends on every path.

The prose still says three transports, because that count is right: the
interactive commands and `app-server` share the canonical-home helper and
differ only in options. The document already counted three over four
branch rows before this change, so the convention was mechanism-based
already; the sentence now says so instead of leaving the reader to infer
it from a table that has grown a row.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0134wwvqFQA87kf1K2cLeEk6
@possibilities

Copy link
Copy Markdown
Contributor Author

Thanks — four findings, three fixed and one declined with reasoning. Pushed as 07d0162.


✅ greptile P1 — test/codex-bin-wrapper.test.ts, Windows close marker is unreliable

Valid, and fixed. The mechanism is exactly as described: stopRuntimeRotationAppHelper terminates unconditionally on Windows, the child never runs its SIGTERM handler, so the proxy fixture's close line never lands and the assertion burns its five seconds before failing — on a run where cleanup did precisely what it should.

It cannot reach CI (the vitest matrix is ubuntu-latest; the one windows-latest job runs typecheck:scripts only), but it would hit anyone running the suite on Windows, and it was a fragility I introduced in the previous round by dropping the idle-timeout override to make the assertion meaningful.

Both app-server tests now assert the helper's liveness rather than its graceful shutdown — CODEX_MULTI_AUTH_TEST_PROXY_MARKER_PID=1, read the pid out of the marker, poll isProcessAlive until false. That is true on every platform, since termination is termination. It reuses the pattern already in this file at the SIGKILL-escalation test.

Importantly it keeps the regression value: I verified by hand-reverting the detach-grace change that both tests still fail without it.

✅ coderabbit — qualify app-server routing in both references

Valid. app-server help / generate-ts / generate-json-schema, and --help/-h on a request command, skip the transport entirely, and neither page said so. Added to docs/configuration.md, docs/reference/commands.md, and the ASCII diagram in docs/development/ARCHITECTURE.md.

⚠️ coderabbit — update the transport count and system diagram

Half right, and I have fixed the half that is.

The diagram was stale and is now corrected — more so than reported. It listed three branches and had been missing resume/fork since #648 moved them to the canonical home, so it predates this PR. It now lists every branch under the transport it takes, including app-server and its detachOnExit: false difference, plus the no-transport help path.

The count of three is still correct, so I kept it. The decisive detail is that before this PR the document already said "one of three transports" over four branch rows — TUI, resume/fork, app, everything else. So the counting convention in this file was already mechanism-based, and a row-per-transport reading contradicts its own established usage rather than just my preference.

app-server reuses the canonical-home app helper that the interactive commands already use; detachOnExit is lifecycle and proxyAppServerAccountRead is a wrapper-side protocol rewrite, and neither changes home placement or how traffic reaches the proxy. The three mechanisms are unchanged: canonical-home helper, shadow-home helper for codex app, inline shadow home for everything else. What actually needed fixing was that the convention was implicit, so the sentence now states it. Say the word if you'd rather count rows and I'll make it four.

❌ coderabbit — add app-server-specific Windows cleanup-retry coverage

Declining, with reasoning rather than a shrug.

To be accurate about what exists: the parent-side backstop — removeDirectoryWithRetry(helperShimDir) after stopRuntimeRotationAppHelper — has no simulation hook and is untested for any branch. The existing "starts an automatic helper and retries transient app-server shim file operations" test covers the install-time retries inside installRuntimeRotationAppServerCliShim, which run in the helper, on the codex app branch. So "already covered" would have been the wrong answer.

And yes, app-server exit reaches the backstop differently — in frequency and reliance, not in code. A clean TUI exit detaches and skips it entirely; a clean app-server exit now always stops the helper and takes it. On Windows the stopped helper is killed unconditionally and never runs its own shim cleanup, so that backstop is the only shim removal for every app-server exit there.

I am still declining, for narrower reasons than I first gave: the code is shared and unmodified by this branch, its failures are swallowed by design, and stale directories are swept on the next launch by sweepStaleRuntimeRotationAppServerShimDirs, so the worst case is a temporarily stale directory. Testing it would need a new simulation hook — genuine follow-up material, and I'm happy to open a separate issue for it if you want the coverage.


Gate after the changes: lint, typecheck, build, documentation.test.ts 32/32 all clean; codex-bin-wrapper.test.ts 131 passed / 3 failed, those three being the pre-existing Windows-path tests that fail identically on an unpatched checkout of this machine.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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

Inline comments:
In `@docs/configuration.md`:
- Line 143: Add an upgrade note to docs/upgrade.md documenting the app-server
routing behavior implemented near the codex.js app-server handling: it remains
on the canonical CODEX_HOME while receiving the runtime proxy provider through
-c overrides, unlike surfaces that may use a shadow home. Do not add npm
scripts.

In `@docs/reference/commands.md`:
- Line 550: Update the command reference paragraph describing transport bypasses
to include app-server --help and app-server -h alongside app-server help,
generate-ts, and generate-json-schema. Keep the existing explanation and
behavior unchanged while documenting both flag forms.
🪄 Autofix

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 Plus

Run ID: df9c341a-a7b5-4bf0-a50b-133bbd47b759

📥 Commits

Reviewing files that changed from the base of the PR and between 79f492d and 07d0162.

📒 Files selected for processing (4)
  • docs/configuration.md
  • docs/development/ARCHITECTURE.md
  • docs/reference/commands.md
  • test/codex-bin-wrapper.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (15)
docs/**/*.md

📄 CodeRabbit inference engine (docs/STYLE_GUIDE.md)

docs/**/*.md: User-facing documentation should follow the page template: Title and one-line lead, Quick path commands, Core operational workflow, Troubleshooting or failure handling, and Related links
Use short sections and scan-friendly tables in documentation where they improve clarity
Prefer direct, actionable language in documentation
Use runnable command examples in documentation
Explain expected outcomes after critical commands in documentation
Keep terminology consistent with runtime names in documentation
Avoid speculative language when behavior is deterministic in documentation
Put the user problem in the first paragraph before implementation detail
Use descriptive page titles such as codex-multi-auth Features instead of generic titles on public docs
Do not repeat keyword lists in every section; search terms should appear only where they help a developer understand the page
Canonical command family is codex-multi-auth ...
Canonical runtime root is ~/.codex/multi-auth
Runtime rotation must be described as default-on unless the release policy changes
Legacy command/path references belong only in migration contexts in documentation
Compatibility aliases (codex multi auth, codex multi-auth, codex multiauth) belong only in command reference, troubleshooting, or migration contexts
Keep command flags aligned with runtime usage text in documentation
Avoid non-runnable command snippets in documentation
Avoid conflicting path guidance across documentation
Avoid legacy-first onboarding language in documentation

Organize repository documentation according to the defined layers: product entry, user operations, reference, and development.

docs/**/*.md: Do not describe codex-multi-auth as replacing @openai/codex or publishing the global codex binary; preserve the official CLI's ownership of codex.
Use codex-multi-auth for account management, and reserve codex-multi-auth-codex or mcodex for intentionally forwarding official Codex commands th...

Files:

  • docs/reference/commands.md
  • docs/configuration.md
  • docs/development/ARCHITECTURE.md
docs/reference/**/*.md

📄 CodeRabbit inference engine (docs/STYLE_GUIDE.md)

New flags/settings/paths must be reflected in docs/reference/*

docs/reference/**/*.md: Keep command, API, error-contract, settings, and storage-path details in the canonical reference documentation.
Document compatibility aliases (codex multi auth, codex multi-auth, and codex multiauth) only in command-reference, troubleshooting, or migration sections.

Files:

  • docs/reference/commands.md
docs/**/*.{md,mdx}

📄 CodeRabbit inference engine (docs/troubleshooting.md)

Document that codex-multi-auth-codex is the optional forwarding wrapper, while codex-multi-auth is the canonical account-manager command family; the package does not publish a global codex binary.

Document the canonical command names, runtime paths, configuration precedence, storage migration behavior, and upgrade procedures consistently across the referenced documentation.

Files:

  • docs/reference/commands.md
  • docs/configuration.md
  • docs/development/ARCHITECTURE.md
**/*

📄 CodeRabbit inference engine (AGENTS.md)

Source changes belong in index.ts, lib/, and scripts/; dist/ is generated output and local temporary/cache directories must not be edited.

Files:

  • docs/reference/commands.md
  • docs/configuration.md
  • docs/development/ARCHITECTURE.md
  • test/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/commands.md
  • docs/configuration.md
  • docs/development/ARCHITECTURE.md
docs/{index.md,getting-started.md,faq.md,architecture.md,features.md,configuration.md,troubleshooting.md,privacy.md,upgrade.md}

📄 CodeRabbit inference engine (docs/DOCUMENTATION.md)

Keep the listed public documentation pages as the canonical sources for operator onboarding, FAQ, architecture, features, configuration, troubleshooting, privacy, and upgrades.

Files:

  • docs/configuration.md
docs/development/**/*.md

📄 CodeRabbit inference engine (docs/DOCUMENTATION.md)

Keep internal architecture, configuration flow, repository ownership, testing, parity, metadata, and audit guidance in development documentation.

Prefer current architecture and reference documentation over historical plans and audit snapshots when describing the present system.

Files:

  • docs/development/ARCHITECTURE.md
docs/development/**/*

📄 CodeRabbit inference engine (docs/development/CONFIG_FLOW.md)

docs/development/**/*: Resolve the runtime root directory in this order: CODEX_MULTI_AUTH_DIR; explicit non-default CODEX_HOME/multi-auth; existing account-storage roots under CODEX_HOME or ~/.codex; canonical ~/.codex/multi-auth; and legacy paths only when storage signals exist.
Read dashboardDisplaySettings and pluginConfig from settings.json, while preserving legacy compatibility loading and migration.
Resolve pluginConfig values using this precedence: existing CODEX_MULTI_AUTH_CONFIG_PATH file, valid unified settings.json configuration, legacy compatibility configuration, then DEFAULT_PLUGIN_CONFIG; apply environment-variable overrides afterward.
Ignore a configured but nonexistent CODEX_MULTI_AUTH_CONFIG_PATH during loading, but create it on the first save while the variable remains set.
Resolve dashboard display values from persisted dashboardDisplaySettings, followed by normalization and fallback defaults.
Resolve account storage by selecting the root directory, using the global accounts file by default, using a project-namespaced path when project-scoped mode is active, and attempting applicable legacy project-file migration.
Normalize standalone codex-multi-auth bare subcommands to auth ... before dispatch; normalize wrapper aliases; run auth-manager commands locally; forward out-of-scope wrapper commands to the official Codex CLI.
For forwarded request-bearing commands, honor runtime rotation: resolve CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY, then pluginConfig.codexRuntimeRotationProxy, which defaults to enabled.
When rotation is enabled for a requesting command, use a per-process-token loopback Responses proxy, a temporary shadow CODEX_HOME, and a rewritten config.toml; synchronize refreshed official Codex state on exit and remove the shadow home.
The runtime proxy must select or refresh managed accounts and rotate on rate-limit, authentication, network, or server failures before streaming begins.
The plugin host m...

Files:

  • docs/development/ARCHITECTURE.md
docs/development/**/*.{md,mdx}

📄 CodeRabbit inference engine (docs/development/TESTING.md)

When documentation changes, verify every command snippet is runnable, path references match runtime modules, cross-links are valid, and the feature matrix matches implemented features.

Files:

  • docs/development/ARCHITECTURE.md
test/**/*.test.ts

📄 CodeRabbit inference engine (test/AGENTS.md)

test/**/*.test.ts: Write Vitest test suites with globals enabled (describe, it, expect)
Maintain 80%+ coverage threshold across statements, branches, functions, and lines
Use removeWithRetry() for Windows filesystem cleanup instead of bare fs.rm to handle EBUSY, EPERM, and ENOTEMPTY errors
Do not rely on dist/ in tests; use source files instead
Do not skip tests without justification
Relax lint rules for test files as configured in eslint.config.js

Files:

  • test/codex-bin-wrapper.test.ts
test/**/codex-bin-wrapper.test.ts

📄 CodeRabbit inference engine (test/AGENTS.md)

Test bin wrapper lazy-load and missing dist handling with concurrent invocations in codex-bin-wrapper.test.ts

Files:

  • test/codex-bin-wrapper.test.ts
**/*.{ts,js,mjs}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,js,mjs}: Use ESM modules throughout the project; the package is configured with "type": "module".
Do not use as any, @ts-ignore, or @ts-expect-error.

Files:

  • test/codex-bin-wrapper.test.ts
test/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Windows-sensitive filesystem tests and helpers must use retry handling for transient lock-related cleanup and write failures.

Files:

  • test/codex-bin-wrapper.test.ts
**/*.{js,ts,mjs,cjs}

📄 CodeRabbit inference engine (README.md)

**/*.{js,ts,mjs,cjs}: Do not publish or replace a global codex binary; official OpenAI installation paths must retain ownership of the codex command.
Keep OAuth credentials local and restrict runtime rotation and local bridges to loopback interfaces.
Require hashed local client tokens to protect the optional loopback bridge.
Responses background: true compatibility must remain opt-in; requests using it must use stateful store=true routing rather than stateless store=false routing.
Never run npm install or update commands automatically; only display a manual upgrade notice when appropriate.
Experimental synchronization and backup flows must be non-destructive by default: preview before applying sync, preserve destination-only accounts, and fail safely on backup filename collisions.
Keep account storage project-scoped under the configured multi-auth root when operating in repo-specific workflows.

Files:

  • test/codex-bin-wrapper.test.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-bin-wrapper.test.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T17:32:00.701Z
Learning: The canonical command family is `codex-multi-auth ...`; the package must not publish a global `codex` binary.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T17:32:00.701Z
Learning: The desktop app bind must be reversible and must not patch official app files.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T17:32:00.701Z
Learning: Default general model routing uses `gpt-5.5`; diagnostic live/quota probes lead with `gpt-5.6-sol`.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T17:32:14.968Z
Learning: Resolve runtime configuration sources in this order: existing `CODEX_MULTI_AUTH_CONFIG_PATH`, valid unified `settings.json` `pluginConfig`, legacy compatibility files, then `DEFAULT_PLUGIN_CONFIG`; apply environment-variable overrides afterward.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T17:32:14.968Z
Learning: Ignore a set-but-missing `CODEX_MULTI_AUTH_CONFIG_PATH` during loading, while continuing to use it as the save target when set.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T17:32:14.968Z
Learning: Honor `CODEX_MULTI_AUTH_DIR` for all multi-auth-owned runtime files; when `CODEX_HOME` is non-default, resolve strictly under `$CODEX_HOME/multi-auth` without scanning other roots.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T17:32:14.968Z
Learning: Keep dashboard display values separate from runtime `pluginConfig`; resolve dashboard values from persisted `dashboardDisplaySettings` followed by normalized defaults.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T17:32:14.968Z
Learning: A per-invocation `--account` or `CODEX_MULTI_AUTH_FORCE_ACCOUNT` pin must be ephemeral, take precedence over the environment selector, require the runtime rotation proxy, and fail hard when the proxy is disabled.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T17:32:14.968Z
Learning: The runtime rotation proxy must preserve request bodies and streaming responses, replace outbound authorization with the selected managed account, rotate before streaming on eligible failures, and remove hop-by-hop, private account metadata, and stale decoded content-encoding headers.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T17:32:14.968Z
Learning: On explicit OAuth token revocation, return the error directly instead of rotating accounts and apply the configured token-invalidation cooldown.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T17:32:14.968Z
Learning: Do not start the runtime proxy or inject provider overrides for `--help`/`-h` and `app-server help`, `generate-ts`, or `generate-json-schema`; the CLI auth-store reconciliation still applies.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T17:32:14.968Z
Learning: In sequential scheduling, retain the active account until it is exhausted, honor manual pins, and intentionally ignore per-session affinity after an account change.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T17:32:14.968Z
Learning: Keep retry and wait budgets bounded so all-account rate-limit waits cannot exceed the host client's request timeout.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T17:32:14.968Z
Learning: Package install scripts must remain side-effect-free; postinstall may only print a short notice.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T17:32:14.968Z
Learning: Do not patch official Codex app files; use the supported persistent localhost router and restore the backed-up Codex configuration when rotation is disabled or unbound.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T17:32:14.968Z
Learning: The wrapper must never run npm install or update commands automatically; version checks may only print a manual installation notice.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T17:32:14.968Z
Learning: Deprecated selectors such as `gpt-5-codex` and `gpt-5.1-codex*` must be treated as compatibility aliases and retried only after a real unsupported-model response; fallback to `gpt-5.4` only after that response.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-10T17:32:40.515Z
Learning: Compatibility command forms and documented exit-code, JSON, CSV, and flag behavior should remain stable because they constitute the CLI command contract.
📚 Learning: 2026-06-04T06:14:18.093Z
Learnt from: ndycode
Repo: ndycode/codex-multi-auth PR: 510
File: test/scheduling-strategy-config.test.ts:1-1
Timestamp: 2026-06-04T06:14:18.093Z
Learning: In ndycode/codex-multi-auth, do not flag explicit imports from "vitest" (e.g., describe, it, expect, beforeEach/afterEach, etc.) in test files as issues—even if the Vitest config sets `globals: true`. The repo’s established convention is to keep these imports for consistency with neighboring tests; removing them would make files outliers.

Applied to files:

  • test/codex-bin-wrapper.test.ts
📚 Learning: 2026-06-04T06:14:24.975Z
Learnt from: ndycode
Repo: ndycode/codex-multi-auth PR: 510
File: test/runtime-rotation-proxy.test.ts:2478-2491
Timestamp: 2026-06-04T06:14:24.975Z
Learning: In ndycode/codex-multi-auth test files (e.g. `test/*.test.ts`), when creating V3 storage fixtures for accounts, it’s an intentional convention to use `as never` for deliberately minimal stored-account objects that only include `refreshToken`, `addedAt`, and `lastUsed`. Do not treat `as never` here as a type-safety problem: optional/other fields are expected to be populated by the runtime during execution, and the cast is used solely to keep the fixture minimal and consistent across existing tests.

Applied to files:

  • test/codex-bin-wrapper.test.ts
🪛 ast-grep (0.45.0)
test/codex-bin-wrapper.test.ts

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { type SpawnSyncReturns, spawn, spawnSync } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { type SpawnSyncReturns, spawn, spawnSync } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { type SpawnSyncReturns, spawn, spawnSync } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { type SpawnSyncReturns, spawn, spawnSync } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🔇 Additional comments (2)
test/codex-bin-wrapper.test.ts (1)

2177-2177: LGTM!

Also applies to: 2210-2224, 3235-3235, 3255-3269

docs/development/ARCHITECTURE.md (1)

58-63: LGTM!

Also applies to: 72-75, 171-178, 193-194

Comment thread docs/configuration.md Outdated
Comment thread docs/reference/commands.md Outdated
Review round 3, both docs-only.

The help exception named `app-server help` / `generate-ts` /
`generate-json-schema` but scoped the flag forms to request commands.
`app-server` is not one of those: the wrapper checks
`hasHelpFlagAfterCommand` inside its own branch, so `app-server --help`
and `app-server -h` skip the transport too and the reference did not say
so.

`docs/upgrade.md` gains a bullet under the existing runtime-rotation
note. Nothing migrates, which is why this is a bullet rather than a
section — that file is topic-scoped, and an app-server transport change
is a runtime-rotation topic — but the observable behavior does change for
anyone launching app-servers through the wrapper: no shadow home appears
for the command, and the proxy moves from running inline in the wrapper
to the same detached helper the interactive TUI uses, which stops when
the server exits.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0134wwvqFQA87kf1K2cLeEk6
@possibilities

Copy link
Copy Markdown
Contributor Author

Both accepted — pushed as a6b4c7f.

✅ document the app-server help flag bypass

Correct, and my wording did exclude them. app-server is not in the requestCommands set; the wrapper checks hasHelpFlagAfterCommand inside the app-server branch itself, so --help/-h there skip the transport by a different route than the one my sentence described.

docs/reference/commands.md and docs/configuration.md now say --help/-h after any of these commands, app-server --help and app-server -h included, alongside the help / generate-ts / generate-json-schema subcommands. "After" is deliberate: codex --help app-server puts the flag ahead of the command and still takes the transport, which is pre-existing and shared by every command, so the sentence stays literally true. The ASCII diagram in docs/development/ARCHITECTURE.md already said "on any of these" and needed no change.

✅ add the app-server routing note to docs/upgrade.md

Fair, and I had ticked that box as n/a on the wrong reasoning — I read the file as migration-only, but it is topic-scoped, and this is squarely a runtime-rotation topic. Added as a bullet under the existing Runtime Rotation Upgrade Note rather than a new section, since nothing migrates.

Worth noting one correction I made to my own bullet before pushing: I had written that the helper "stops when the server exits rather than idling on," which implies a baseline that never existed — on 2.8.3 app-server had no helper at all, since the shadow branch starts the proxy inline in the wrapper process. The genuinely new observable runs the other way, so the bullet now says the proxy moves into the same detached helper the interactive TUI uses, visible as a --codex-multi-auth-runtime-app-helper process while the server runs, and stops when the server exits.


documentation.test.ts 32/32. Docs-only this round, so the wrapper suite is unchanged from 07d0162.

ndycode added a commit that referenced this pull request Aug 11, 2026
Replaces the placeholder created by the version bump, now that 2.8.4 is
published to npm.

Covers both #659 reproductions from the server's point of view (the
lstat-strict app-server-control check and the frozen thread index), the
three app-helper behaviours that were wrong for a resident server, and
the operator-visible consequences: no shadow home, a helper stopped with
the server rather than idled out, and a proxy failure that now fails the
server instead of running it unrotated.

Credits @possibilities for the report and the original patch in #660.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KT5pNBtZ151FSC6KBA32sk
@ndycode

ndycode commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Superseded by #662, which is merged (d5f76ab) and closed #659.

Verified rather than assumed — this branch is strictly behind main, so merging it now would be a net regression:

  • git diff --stat main..pr660 is +184 / −437: it would remove 402 lines main already has.
  • test/codex-bin-wrapper.test.ts would shrink from 6746 to 6563 lines.
  • The app-server shim guard added for [bug] codex-multi-auth-codex app-server cannot run on the shadow CODEX_HOME #659 (APP_RUNTIME_HELPER_INSTALL_APP_SERVER_SHIM_ENV and the installAppServerShim gate) and the runtimeProxyContext.startupError handling are on main and absent here.
  • Every marker from this branch's later commits is present on main at equal or higher count — detachOnExit 7 vs 6, grace 13 vs 10, alive 8 vs 8, shim 111 vs 100 — so nothing unique to this branch is lost.

GitHub already marks it CONFLICTING/DIRTY for the same reason. Closing to keep the queue honest; reopen if I've missed something. Thanks @possibilities.

@ndycode ndycode closed this Aug 11, 2026
@ndycode

ndycode commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Thank you for this — the report in #659 was one of the best I've received. A reproduction that isolates the trigger (ls -ld ~/.codex/app-server-control, then confirming removal makes it start), a second independent failure mode with the state_5.sqlite sizes to prove it, and the /tmp-is-a-symlink-on-macOS aside so nobody else loses an hour to it. That is most of the diagnostic work, and the routing change here was correct.

The fix shipped in v2.8.4https://github.com/ndycode/codex-multi-auth/releases/tag/v2.8.4 — via #662, which builds on your branch. You are credited in the release notes and the changelog. I'm closing this one as superseded rather than merging it, because the follow-up review turned up something neither of us had accounted for.

What changed from your patch

The app-server CLI shim comes along with the transport. installRuntimeRotationAppServerCliShim is reachable only from runRuntimeRotationAppHelper, so moving app-server onto the app-helper silently installs it — and it mutates the environment the forwarded server inherits in three ways the shadow path never did:

Variable Value Consequence
CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY 0 Codex passes its env to shell tools and MCP servers, so every nested codex-multi-auth-codex run inside an agent session reads rotation as disabled and bills whatever account the official CLI resolves. A nested --account fails outright.
CODEX_CLI_PATH the shim dir The file named codex there is a copy of node; any subcommand other than app-server resolved through it executes bare node with no script.
NODE_OPTIONS --import=<shim>/…preload.mjs Inherited by every Node subprocess — on a directory removed at wrapper exit and swept on the next helper start.

That first row is the one that matters: it's precisely the silent billing error your Risk section argued hard-fail was worth having. So v2.8.4 gates the shim off for this branch (installAppServerShim: false). It exists only so the Codex desktop app can have its own app-server spawn intercepted; a wrapper-invoked server already carries the overrides on its command line. codex app and the interactive branches keep it unchanged.

A knock-on: with no shim there's no CODEX_MULTI_AUTH_APP_SERVER_ACCOUNT_LABEL, which makes your proxyAppServerAccountRead option genuinely load-bearing. It wasn't on this branch — the shim set the same label unconditionally, so forwardToRealCodex's || already yielded true, and both app-server tests asserted APP_SERVER_LABEL:1, which reads the shim's variable and passes with the option deleted. I verified the difference by deleting it in the new branch and watching the two account-response tests fail.

Three smaller ones:

  • Your detachOnExit grace rewrite is right, but startedAt is still captured before a launch bounded at APP_RUNTIME_HELPER_LAUNCH_TIMEOUT_MS (15s). On a loaded machine the whole 5s window can elapse during startup, so codex app — which relies on the window rather than an explicit flag — kills the helper it just handed the desktop app off to. The clock now starts when the helper reports ready.
  • The hard-fail is the right call for this surface, and I kept it. But "the wrapper exits" was ERR_UNHANDLED_REJECTION with a raw stack trace, plus a leaked compatibility mkdtemp — nothing between createRuntimeRotationAppHelperContext and the module's top-level await main() catches. It's now a one-line diagnostic and exit 1, with the compatibility home released first.
  • Your reviewer's P1 about the Windows close marker was correct, and I hit it independently: on Windows a stopped helper is hard-terminated and never reaches the handler that appends close, so the marker assertion times out even though cleanup worked. The tests now poll the helper pid instead — which also still fails for a stranded helper, so your point about not shortening CODEX_MULTI_AUTH_APP_ROTATION_IDLE_MS (making the stranding fix regression-tested rather than asserted) survives intact. That framing was a good idea and I kept it.

On the transport-count disagreement

You flagged the docs/development/ARCHITECTURE.md count as the one open item for a human to settle, and your reasoning was sound — rows and transports were never 1:1 in that file, so reading a new row as a new transport did contradict its own usage.

I went a third way rather than picking a side: the sentence now names both dimensions instead of overloading one number — two homes, canonical and shadow, reached over four selection branches, with a note that the table splits the first branch into its two predicates. The diagram header was updated to match, which is what actually made the page self-contradictory.

Also worth your time

The multi-account balancer you described — one account-pinned app-server per account — has a real problem left in it that v2.8.4 does not fix. resolveRuntimeRotationAppHelperStatusPath returns a single global path with no pid component, and every helper republishes it about once a second. N pinned servers means N helpers last-writer-wins the same file, and the first to exit stamps it stopped while the others are live; rotation status and runtime-current-account then report an arbitrary or dead helper. It's tracked in #662's follow-ups. If you want to take it, I'd review it gladly — you clearly have the workload that exercises it.

Thanks again.

ResponseIV pushed a commit to ResponseIV/codex-multi-auth that referenced this pull request Aug 13, 2026
`codex app-server` took the shadow-`CODEX_HOME` transport, and a resident
server cannot live there. Codex applies an lstat-strict check to
`<CODEX_HOME>/app-server-control` and refuses to start when it is a symlink,
which is what the shadow mirror makes of it; and a server that does start
hands every attached client a frozen snapshot of the thread index for the
life of the process, writing anything it creates into a copy discarded at
exit. Route it to the canonical-home app helper, alongside the interactive
TUI and `resume`/`fork` (ndycode#647/ndycode#648).

Do not install the app-server CLI shim on that branch. The shim is reachable
only from the app helper, so moving the transport silently drags it in, and
it stamps `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=0`, `CODEX_CLI_PATH`, and
a preload `NODE_OPTIONS` onto the environment the forwarded child inherits.
Codex passes that environment to shell tools and MCP servers, so every nested
wrapper invocation would read rotation as disabled and bill whatever account
the official CLI resolved. Its only purpose is to intercept the desktop app
spawning its own app-server; a wrapper-invoked server already carries the
overrides on its command line. `codex app` and the interactive branches keep
it, unchanged.

Because no shim means no account-label env, request the `account/read` /
`getAuthStatus` / `account/rateLimits/read` rewriting explicitly through
`proxyAppServerAccountRead`.

Also on the shared helper path:

- Honor an explicit `detachOnExit: false`, so a short-lived app-server no
  longer strands its helper for the full 12h idle timeout.
- Start the detach-grace clock when the helper is ready rather than before a
  launch bounded at 15s, so `codex app` cannot kill the helper it just handed
  the desktop app off to.
- Turn a helper that cannot start into a diagnostic and exit 1 instead of an
  unhandled rejection with a leaked compatibility home. Hard-fail is
  deliberate: there is no rotation-off shape to degrade into, and a resident
  server that quietly loses rotation is a billing error you cannot see.
- Stop accumulating the helper startup stdout/stderr buffers once startup
  settles; the listeners stay attached so the helper never blocks on a full
  pipe.

Reported with a complete reproduction by Mike Bannister (@possibilities) in
ndycode#659; the core routing change is from their ndycode#660.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] codex-multi-auth-codex app-server cannot run on the shadow CODEX_HOME

2 participants