Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(codex): stop runtime helpers from leaking past their idle timeout (#663) - #664

Merged
ndycode merged 3 commits into
ndycode:mainfrom
possibilities:fix/runtime-helper-leak
Aug 13, 2026
Merged

ndycode merged 3 commits into
ndycode:mainfrom
possibilities:fix/runtime-helper-leak

Conversation

@possibilities

@possibilities possibilities commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Stops runtime rotation app helpers from leaking past their idle timeout, and
stops N helpers from trampling one shared status file. Closes #663.

Observed before the fix: 183 concurrent helpers, 5.58 GB RSS, 41 past the
12-hour idle timeout (oldest 33 h), refilling at 10–28/hour under ordinary
use; 701 orphaned owner files; one status file rewritten ~183×/s. All
defective sites date to 1bc40eb.

What changed

scripts/codex.js

  • Owner liveness is identity-checked. kill(pid, 0) answers "does a
    process hold this integer", never "is this still my launcher" — and because
    the idle deadline only ever moves forward, one recycled-PID false positive
    per 12 h window makes a helper immortal. The launcher now states its
    identity at spawn (PID + kernel start time via
    CODEX_MULTI_AUTH_APP_ROTATION_OWNER_START_TIME_MS, captured with
    ps -o lstart= under LC_ALL=Clstart is locale-formatted and a
    localized string can fail Date.parse, which would silently disable the
    fix); the helper requires both halves to match and both always come from
    the launcher's own capture, never an inherited environment value. It
    re-verifies at most once a minute (a process spawn per 1 Hz tick would cost
    more than it saves, and the ps call carries a 2 s timeout so a wedged
    read cannot hang the proxy's event loop); a failed re-read keeps the
    previous verdict — under the process-table pressure this fix exists for,
    fork itself can fail, and declaring a live owner dead would kill the
    proxy out from under an active session. Where no start time is known at
    all (no ps, or a pre-upgrade launcher) the check degrades to bare
    liveness. The EPERM-tolerance test now runs with a real matching start
    time, so it covers the production configuration — EPERM through the
    identity branch — and doubles as the false-positive guard: a matching
    identity must keep a live owner's helper alive.
  • An absolute lifetime ceiling (CODEX_MULTI_AUTH_APP_ROTATION_MAX_LIFETIME_MS,
    default 24 h, 0 disables) sits beside the idle check, deliberately
    unconditional on activity: it exists for exactly the case where activity
    accounting is wrong again, converting any future unbounded leak into a
    bounded one.
  • Status telemetry is per-helper. Each helper publishes
    runtime-rotation-app-helper.<pid>.json (mirroring the owner files),
    publishes on change plus a heartbeat of min(60 s, idleTimeout) instead of
    every tick, and stamps a terminal state on exit. The ready line's
    statusPath points at the per-PID file.
  • Metadata lifecycle is closed. A helper removes its own owner file on
    exit (no post-mortem value); each launcher sweeps per-PID status/owner
    files whose helper PID is dead — and the legacy shared status file once the
    PID recorded inside it is dead — before spawning. A live kill(pid, 0) is
    not taken as proof of life here either: when the file records when its
    helper started, a current PID-holder whose kernel start time is meaningfully
    later cannot be that helper, so a recycled PID cannot shield a stale file
    from every future sweep. Terminal status stamps therefore survive until the
    next launch, long enough to be read without accumulating forever. First
    launch on the affected machine swept 694 stale owner files.

lib/runtime/runtime-current-account.ts, lib/codex-manager/commands/rotation.ts

  • Both readers scan the per-PID files, prefer the most recently updated
    live running helper, fall back to the freshest terminal stamp, and
    still read the legacy un-suffixed path so a pre-upgrade helper stays
    visible during the transition. rotation status appends
    (+N more running) when several live helpers exist — with one pinned
    app-server per account that is the normal state, and the old line implied
    the arbitrary last writer was the only helper. Both keep their own
    hardened local reader by the codebase's existing convention.

lib/runtime/app-bind.ts

  • Unbind — and therefore codex-multi-auth uninstall — now walks every
    per-PID status file plus the legacy path through the same per-helper logic
    the single shared file used to get: ownership verification (status/owner
    identity-token agreement plus process-identity checks) gates every stop, so
    unbind reaps each helper it can prove is one of ours and preserves — with a
    warning — anything it cannot. Without this, moving status to per-PID files
    would have made unbind silently stop nothing while reporting success.

Tests

Four new cases in test/codex-bin-wrapper.test.ts, each verified to fail
with its defect hand-reverted:

  • PID reuse does not extend life — a helper pointed at a live process
    with a mismatched start time idles out on schedule; fails against the bare
    kill(pid, 0) check.
  • Max lifetime is enforced — a genuinely live owner does not carry a
    helper past the ceiling.
  • One status file per helper PID — two concurrent helpers, two files,
    each reporting its own PID; the shared legacy path is never written.
  • Owner file removed on exit; dead helpers' metadata swept on the next
    launch
    — including a stale legacy status file.

The identity mechanism is mutation-verified: stubbing the start-time read to
null (the fully-disabled state) flips the mismatch test to failing, so the
suite pins that the read succeeds and is compared, not merely that the code
path exists. New unit coverage for the reader migration: per-PID preference
over a fresher-but-dead record, terminal-stamp fallback
(test/runtime-current-account.test.ts), the (+N more running) status line
with a dead PID counted for nothing
(test/codex-manager-rotation-command.test.ts), and per-PID unbind cleanup
(test/app-bind.test.ts).

Two existing tests updated only in where they read status (per-PID glob
instead of the legacy path); the EPERM test gains the production start-time
env as described above; every pre-existing unit test in both reader suites
passes unchanged, which is the legacy-fallback working. No detach semantics
changed and the shutdown tests pass untouched.

Validation

  • npm run lint / npm run typecheck / npm run build
  • npx vitest run test/codex-bin-wrapper.test.ts test/runtime-current-account.test.ts test/codex-manager-rotation-command.test.ts test/documentation.test.ts
    only the pre-existing macOS flakes fail (the shim file-op retry timeout and
    the Windows-path resolver trio, which fail intermittently on clean main
    under full-file load as well)
  • Live validation on the affected machine: first launcher run swept 701→7
    owner files; a real helper carries the identity env and publishes its
    per-PID status; rotation status reports the newest live helper with
    (+1 more running) across one pre-upgrade (legacy-file) and one new
    (per-PID) helper simultaneously; SIGTERM to a helper writes its terminal
    stamp and removes its owner file.

Risk and rollback

Risk: low-medium. The reaper change strictly widens the conditions under
which a helper exits (identity mismatch, lifetime ceiling); it never keeps a
helper alive longer than today. The status migration keeps the legacy path
readable; the only consumers of the shared file in-tree are the two migrated
readers and app-bind.ts (see above). On platforms without ps, every new
check degrades to today's behavior exactly.

Rollback: revert the commit. Per-PID status/owner files from the interim
are swept by any subsequent launcher (old code ignores them; they are small
and inert).

Two costs stated plainly: the launcher adds one synchronous ps spawn
(~5 ms measured) to each CLI invocation that starts a helper, and ps is
resolved via PATH like every other external binary the wrapper shells out
to.

Known follow-ups (deliberately out of scope)

  1. A wrapper killed by a signal skips helper stop. Cleanup runs when the
    wrapper's child exits, not when the wrapper itself receives SIGTERM — so a
    supervised restart that signals the wrapper strands its helper for the
    idle window (now bounded at 12 h idle / 24 h ceiling rather than
    immortal). Wrapper-level signal handlers are the real fix and deserve
    their own change.
  2. A direct liveness handle would remove the polling class entirely. An
    extra pipe FD from launcher to helper EOFs on launcher death, whatever the
    cause — no PID, no reuse, no ps. EOF must mean "start the idle
    countdown", not "exit now" (the codex app handoff depends on helpers
    outliving their launcher). The start-time check in this PR is the smaller,
    mergeable version; happy to follow up with the handle if you want it.
  3. A concurrent-helper cap at spawn (reap oldest idle before spawning
    past N) would be a second backstop; 183 concurrent helpers should never
    have been reachable.
  4. The lib readers still trust state === "running" + kill(pid, 0) when
    picking the live helper to display. A SIGKILLed helper whose PID is later
    recycled can be reported as current until the next launcher sweep removes
    its file (the sweep itself is identity-checked, so the file does not
    survive past that). Threading start-time identity through the TypeScript
    readers is mechanical but widens the diff; happy to follow up.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FW2tPyLdcRXrnGsYVVJeEj

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

the pr bounds runtime helper lifetime, adds launcher identity checks, and migrates helper telemetry from one shared file to per-pid records.

  • sweeps stale owner and status metadata before helper launch.
  • updates status readers and unbind cleanup for concurrent helpers.
  • adds vitest coverage for pid reuse, lifetime limits, metadata cleanup, status selection, and unbind behavior.

Confidence Score: 5/5

the pr appears safe to merge.

no blocking failure remains.

Important Files Changed

Filename Overview
scripts/codex.js adds bounded helper lifetime, process-identity liveness checks, per-pid telemetry, and stale metadata sweeping.
lib/runtime/app-bind.ts extends unbind to enumerate and safely stop verified per-pid helpers.
lib/runtime/runtime-current-account.ts selects the freshest live per-pid helper while retaining legacy and terminal fallbacks.
lib/codex-manager/commands/rotation.ts reports the preferred live helper and concurrent running-helper count.
lib/runtime-constants.ts centralizes per-pid helper status discovery while preserving the legacy path.
test/codex-bin-wrapper.test.ts covers pid reuse, owner identity, maximum lifetime, per-helper files, and metadata sweeping.
test/app-bind.test.ts covers per-pid unbind ownership verification and metadata cleanup.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[launcher] --> B[sweep stale metadata]
  B --> C[spawn helper]
  C --> D[write per-pid owner metadata]
  C --> E[write per-pid status heartbeat]
  E --> F[status readers]
  D --> G[unbind ownership verification]
  E --> G
  G --> H[stop verified helpers]
  C --> I{idle or lifetime limit}
  I --> J[terminal status]
  J --> K[remove owner metadata]
Loading

Reviews (3): Last reviewed commit: "test(codex): pin the sweep-retry test to..." | Re-trigger Greptile

Context used:

The idle reaper's owner check was a bare kill(pid, 0), which answers
"does a process hold this integer", never "is this still my launcher".
A recycled PID at one tick pushes the deadline forward 12 hours, and the
deadline only ever moves forward, so one false positive is never
corrected — helpers were observed 33 hours past their timeout, 183
concurrent, 5.6 GB RSS. Owner liveness is now PID plus the launcher's
kernel start time (read under LC_ALL=C so locale cannot disable the
check), re-verified at most once a minute; a failed re-read keeps the
previous verdict instead of declaring a live owner dead, and where no
start time is known the check degrades to bare liveness. An absolute
lifetime ceiling (24h default, CODEX_MULTI_AUTH_APP_ROTATION_MAX_LIFETIME_MS)
bounds the leak if activity accounting is ever wrong again.

Status telemetry is per helper PID instead of N writers last-writer-
winning one file at 1 Hz, published on change plus heartbeat; readers
prefer the newest live helper and still read the legacy path, and
app-bind unbind walks every per-PID candidate through the same
ownership-verified stop it applied to the shared file. Helpers remove
their owner file on exit; launchers sweep metadata whose helper PID is
dead — or provably recycled, by comparing the PID's kernel start time
against the file's own timestamps — before spawning.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01FW2tPyLdcRXrnGsYVVJeEj
@possibilities
possibilities requested a review from ndycode as a code owner August 11, 2026 18:05
@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 11, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

severity: major. this fixes pid reuse, unbounded helper lifetime, status-file contention, and stale metadata risks. the main architectural decisions are pid-plus-kernel-start-time validation, per-pid status files with legacy fallback, bounded helper lifetime, and verified multi-helper cleanup. regression tests cover posix and windows paths, cleanup, status selection, migration, and concurrent helpers.

  • scripts/codex.js:... adds owner identity checks, periodic revalidation, a configurable maximum lifetime, per-pid status files, reduced status publishing, terminal-state publishing, and stale metadata sweeping.
  • lib/runtime/app-bind.ts:... validates all discovered helper records before stopping helpers or removing status and owner metadata.
  • lib/runtime/runtime-current-account.ts:... and lib/codex-manager/commands/rotation.ts:... share helper discovery, prefer live running helpers, count only running helpers, and retain legacy-file read support.
  • lib/runtime-constants.ts:... centralizes per-pid status-path discovery and legacy-path compatibility.
  • docs/configuration.md:... documents CODEX_MULTI_AUTH_APP_ROTATION_MAX_LIFETIME_MS, with a 24-hour default and 0 to disable the limit.
  • regression coverage exists in test/codex-bin-wrapper.test.ts:..., test/app-bind.test.ts:..., test/runtime-current-account.test.ts:..., and test/codex-manager-rotation-command.test.ts:....

review risks:

  • security risk: identity checks must fail safely when process start time is unavailable. verify that failed rechecks cannot authorize cleanup of another process.
  • concurrency risk: shared scanning, stale sweeping, helper exit cleanup, and unbind can race. verify that cleanup cannot remove metadata for a newly created helper.
  • windows risk: verify asynchronous start-time probing, transient file-lock retries, bounded sweep probes, and unreadable directory handling.
  • test risk: confirm that the windows and concurrent cleanup tests run in continuous integration. add coverage if those paths are only covered by local or live checks.

Walkthrough

runtime rotation helpers now use PID-specific status files, launcher start-time identity checks, idle and maximum lifetime limits, stale metadata cleanup, and live-helper-aware status selection. tests cover lifecycle limits, concurrent helper isolation, status discovery, and unbind cleanup.

Changes

runtime helper lifecycle

Layer / File(s) Summary
helper identity and lifetime controls
scripts/codex.js:88, scripts/codex.js:3879, scripts/codex.js:3965, scripts/codex.js:4271, scripts/codex.js:4431, scripts/codex.js:4533, docs/configuration.md:76, docs/development/CONFIG_FIELDS.md:270
helpers record owner PID and start time. identity checks use cached process-start data. helpers stop after idle timeout or maximum lifetime.
per-process status publication and selection
scripts/codex.js:3792, scripts/codex.js:4050, lib/runtime-constants.ts:12, lib/runtime/runtime-current-account.ts:155, lib/codex-manager/commands/rotation.ts:549
helpers publish PID-specific status files. readers retain legacy-file compatibility, prefer live helpers, and fall back to the newest valid status.
unbind and metadata cleanup
lib/runtime/app-bind.ts:1502, lib/runtime/app-bind.ts:1652, test/app-bind.test.ts:1133, docs/privacy.md:91, docs/privacy.md:118
unbind discovers helper records, verifies ownership before stopping helpers, and removes safe status and owner files.
lifecycle and concurrency regression coverage
test/codex-bin-wrapper.test.ts:3238, test/codex-bin-wrapper.test.ts:3311, test/codex-bin-wrapper.test.ts:3433, test/codex-manager-rotation-command.test.ts:449, test/runtime-current-account.test.ts:545
tests cover identity mismatches, lifetime shutdown, concurrent helper isolation, stale metadata cleanup, and live-helper selection.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested labels: bug

Suggested reviewers: ndycode

Sequence Diagram(s)

sequenceDiagram
  participant Launcher
  participant RuntimeAppHelper
  participant OwnerProcess
  participant StatusFiles
  participant RotationStatus
  Launcher->>RuntimeAppHelper: pass owner PID and start time
  RuntimeAppHelper->>OwnerProcess: verify process identity
  RuntimeAppHelper->>StatusFiles: publish PID-specific status
  RotationStatus->>StatusFiles: discover legacy and PID-specific files
  RotationStatus-->>RotationStatus: select newest live helper
Loading

the regression coverage does not include a windows-specific process-start lookup case. concurrent helper isolation is covered in test/codex-bin-wrapper.test.ts:3311.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ⚠️ Warning the title uses valid conventional-commit syntax and matches the change, but it is 76 characters and exceeds the 72-character limit. shorten the summary to 72 characters or fewer while keeping lowercase imperative wording.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed the implementation addresses issue #663 with identity-aware reaping, bounded lifetime, per-helper status, legacy fallback, cleanup, and regression coverage.
Out of Scope Changes check ✅ Passed the changes remain within issue #663, including implementation, documentation, compatibility handling, cleanup, and regression tests.
Description check ✅ Passed the description clearly covers the change, validation, risks, rollback, and follow-ups, but it omits the governance checklist and additional-notes sections.
✨ 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.

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

Caution

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

⚠️ Outside diff range comments (1)
lib/codex-manager/commands/rotation.ts (1)

639-641: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

the terminal-state list does not know about the new max-lifetime state.

scripts/codex.js:4360 publishes state: "max-lifetime" on the ceiling exit, and scripts/codex.js:4370 publishes "error". lib/codex-manager/commands/rotation.ts:639 still tests only "stopped" and "idle-timeout". a helper that hit the ceiling therefore falls through to the running branch, and isProcessAlive is the only remaining gate — the exact gate this PR just proved unreliable under pid reuse. the reader also selects the freshest terminal stamp when nothing is live, so a max-lifetime record is a normal thing to hit here.

invert the check: treat only "running" as running.

🐛 treat any non-running state as not running
 	const alive = isProcessAlive(status.pid);
-	if (!alive || status.state === "stopped" || status.state === "idle-timeout") {
+	if (!alive || status.state !== "running") {
 		return "Codex app helper: not running";
 	}

lib/runtime/runtime-current-account.ts:205 already uses the !== "running" form, so this also removes the divergence between the two readers. please add a rotation-status regression case for a max-lifetime record.

🤖 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 `@lib/codex-manager/commands/rotation.ts` around lines 639 - 641, Update the
rotation status check surrounding the visible terminal-state condition to treat
only status.state === "running" as running; return "Codex app helper: not
running" for max-lifetime, error, stopped, idle-timeout, and any other
non-running state, regardless of isProcessAlive. Add a regression case covering
a max-lifetime rotation record.
🤖 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/development/ARCHITECTURE.md`:
- Line 278: Add a storage-table row for
runtime-rotation-app-helper-owner.<pid>.json, describing it as the
per-live-helper owner metadata file and noting the unsuffixed legacy path if
applicable. Keep the existing runtime-rotation-app-helper.<pid>.json entry
unchanged.

In `@docs/reference/storage-paths.md`:
- Line 162: Update the runtime-rotation helper status-file row in the storage
paths documentation to state that terminal per-<pid> files persist until the
next helper launch removes files for dead PIDs, while the owner’s file is
removed on clean helper exit.

In `@lib/codex-manager/commands/rotation.ts`:
- Around line 577-600: Update the rotation status flow so helper statuses are
read and parsed only once. Have printRotationStatus build the filtered
live-helper array from that single result, derive both the selected status and
liveHelperCount from it, and pass both values explicitly to
formatAppRuntimeHelperStatus instead of relying on countLiveAppRuntimeHelpers or
its default.

In `@lib/runtime/app-bind.ts`:
- Around line 1509-1529: Extract per-PID helper status path discovery into a
shared listRuntimeHelperStatusPaths helper in the runtime constants module,
using APP_RUNTIME_HELPER_STATUS_FILE to build the escaped pattern, filter
supplied directory entries, and append the legacy status path. Update the
app-bind discovery block and the corresponding rotation command logic to call
this helper, removing their duplicated pattern construction and path assembly
while preserving directory-read error handling.
- Around line 1518-1525: The readdir failure handling in the unbind flow must
retry and report errors instead of silently treating helper discovery as empty.
Update the helperStatusNames loading block to invoke readdir through
withFileOperationRetry, and on final failure emit a warning via options.log
before preserving the legacy-only fallback.

In `@scripts/codex.js`:
- Around line 4034-4090: The sweep currently performs an unbounded process-start
lookup for each live-PID candidate, blocking helper startup. Update
sweepStaleRuntimeRotationAppHelperMetadata to cache readProcessStartTimeMs
results by PID for the duration of one sweep and enforce a bounded number of
identity probes, treating candidates beyond the limit as not dead; add a
regression test with several hundred stale per-PID files that verifies the probe
count remains within the bound.
- Around line 3899-3923: Update the helper-side liveness recheck invoked through
createRuntimeRotationAppHelperOwnerLivenessCheck so readProcessStartTimeMs does
not block the live rotation proxy event loop for up to 2 seconds. Prefer an
asynchronous, single-flight ps probe that lets each tick reuse the last verdict
while a probe is pending; ensure a wedged ps cannot spawn overlapping child
processes. Leave the launcher-side synchronous sweep path unchanged.
- Around line 4017-4025: Update removeRuntimeRotationAppHelperOwnerFile and the
other runtime-rotation metadata deletion paths around their existing rmSync
calls to use withSynchronousFileOperationRetry, preserving best-effort cleanup
while retrying transient EBUSY, EPERM, and ENOTEMPTY failures. Add regression
coverage in the existing codex-bin-wrapper tests for transient Windows-lock
cleanup across all three deletion paths.

In `@test/app-bind.test.ts`:
- Around line 1133-1167: Expand the unbind regression coverage around
unbindCodexAppRuntimeRotation to include two dead per-PID status files plus the
legacy status file and assert all are removed in one call. Add an owner file
beside a per-PID record and assert unbind removes it, then add an identityToken
record without a matching owner and verify it remains while options.log receives
the ownership warning. Preserve the existing single-helper coverage.

In `@test/codex-bin-wrapper.test.ts`:
- Around line 3369-3457: Add regression coverage in the helper lifecycle test
around the existing statusFiles polling to verify deduplicated status
publishing: use a sufficiently long idle timeout, identify the running helper’s
status path, sample its statSync(...).mtimeMs, wait across multiple heartbeat
ticks without traffic, and assert the timestamp is unchanged. Keep the existing
sweep, owner cleanup, and terminal idle-timeout assertions intact.
- Around line 3238-3274: Gate the “idles out when the owner PID is alive but its
identity does not match” test to non-Windows platforms. Add a Windows-only
companion test asserting the helper remains alive when owner identity is
unavailable, and ensure this regression suite runs in the Windows workflow;
leave the existing test around line 3279 unguarded.

---

Outside diff comments:
In `@lib/codex-manager/commands/rotation.ts`:
- Around line 639-641: Update the rotation status check surrounding the visible
terminal-state condition to treat only status.state === "running" as running;
return "Codex app helper: not running" for max-lifetime, error, stopped,
idle-timeout, and any other non-running state, regardless of isProcessAlive. Add
a regression case covering a max-lifetime rotation record.
🪄 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: 5e87c0ad-3719-403e-b6e3-21e777665cea

📥 Commits

Reviewing files that changed from the base of the PR and between 92d0f6f and 52d92c1.

📒 Files selected for processing (14)
  • AGENTS.md
  • docs/configuration.md
  • docs/development/ARCHITECTURE.md
  • docs/development/CONFIG_FIELDS.md
  • docs/privacy.md
  • docs/reference/storage-paths.md
  • lib/codex-manager/commands/rotation.ts
  • lib/runtime/app-bind.ts
  • lib/runtime/runtime-current-account.ts
  • scripts/codex.js
  • test/app-bind.test.ts
  • test/codex-bin-wrapper.test.ts
  • test/codex-manager-rotation-command.test.ts
  • test/runtime-current-account.test.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (23)
**/*

📄 CodeRabbit inference engine (AGENTS.md)

Do not edit generated dist/ output or local temporary/cache directories; modify source and regenerate build output instead.

Files:

  • AGENTS.md
  • docs/development/CONFIG_FIELDS.md
  • test/codex-manager-rotation-command.test.ts
  • test/app-bind.test.ts
  • docs/reference/storage-paths.md
  • test/runtime-current-account.test.ts
  • docs/configuration.md
  • docs/development/ARCHITECTURE.md
  • lib/codex-manager/commands/rotation.ts
  • lib/runtime/runtime-current-account.ts
  • docs/privacy.md
  • lib/runtime/app-bind.ts
  • test/codex-bin-wrapper.test.ts
  • scripts/codex.js
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/development/CONFIG_FIELDS.md
  • docs/reference/storage-paths.md
  • docs/configuration.md
  • docs/development/ARCHITECTURE.md
  • docs/privacy.md
docs/development/CONFIG_FIELDS.md

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

Update docs/development/CONFIG_FIELDS.md with field inventory details when adding new configuration fields

Maintain full field inventory in docs/development/CONFIG_FIELDS.md

Files:

  • docs/development/CONFIG_FIELDS.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/CONFIG_FIELDS.md
  • 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/CONFIG_FIELDS.md
  • 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/CONFIG_FIELDS.md
  • docs/development/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/development/CONFIG_FIELDS.md
  • docs/reference/storage-paths.md
  • docs/configuration.md
  • docs/development/ARCHITECTURE.md
  • docs/privacy.md
docs/**/*

📄 CodeRabbit inference engine (docs/privacy.md)

docs/**/*: Keep account and session state local under the configured runtime root; honor CODEX_MULTI_AUTH_DIR and CODEX_MULTI_AUTH_CONFIG_PATH overrides.
Do not add custom analytics, a project-owned remote database, or network destinations beyond the required OpenAI OAuth/backend and GitHub raw/releases endpoints.
Runtime rotation and the optional local bridge must use loopback-only listeners; the bridge must expose only /health, /v1/models, and /v1/responses and require a local bearer token by default.
Store local bridge client tokens as SHA-256 hashes with prefixes and labels; never persist plaintext tokens, which may be shown only during create or rotate operations.
Treat raw request and response body logs enabled by CODEX_PLUGIN_LOG_BODIES=1 as sensitive data; avoid exposing them by default and support appropriate rotation or deletion.
Usage ledger entries must contain only local request metadata summaries; hash email identities and never store prompts, authorization headers, or raw sensitive account identifiers.
Cleanup functionality must remove all canonical local data, including override-root locations when CODEX_MULTI_AUTH_DIR or CODEX_MULTI_AUTH_CONFIG_PATH is configured, on supported platforms.

Files:

  • docs/development/CONFIG_FIELDS.md
  • docs/reference/storage-paths.md
  • docs/configuration.md
  • docs/development/ARCHITECTURE.md
  • docs/privacy.md
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/development/CONFIG_FIELDS.md
  • docs/reference/storage-paths.md
  • docs/configuration.md
  • docs/development/ARCHITECTURE.md
  • docs/privacy.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-manager-rotation-command.test.ts
  • test/app-bind.test.ts
  • test/runtime-current-account.test.ts
  • 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-manager-rotation-command.test.ts
  • test/app-bind.test.ts
  • test/runtime-current-account.test.ts
  • lib/codex-manager/commands/rotation.ts
  • lib/runtime/runtime-current-account.ts
  • lib/runtime/app-bind.ts
  • test/codex-bin-wrapper.test.ts
  • scripts/codex.js
**/*.{ts,js}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,js}: Use ESM-only modules; the package is configured with "type": "module".
Do not use as any, @ts-ignore, or @ts-expect-error.
Runtime rotation must remain enabled by default and stay aligned with explicit release and migration documentation.

Files:

  • test/codex-manager-rotation-command.test.ts
  • test/app-bind.test.ts
  • test/runtime-current-account.test.ts
  • lib/codex-manager/commands/rotation.ts
  • lib/runtime/runtime-current-account.ts
  • lib/runtime/app-bind.ts
  • test/codex-bin-wrapper.test.ts
  • scripts/codex.js
test/**/*.{ts,js}

📄 CodeRabbit inference engine (AGENTS.md)

Windows-sensitive tests and scripts must not use bare recursive deletion without retry handling.

Files:

  • test/codex-manager-rotation-command.test.ts
  • test/app-bind.test.ts
  • test/runtime-current-account.test.ts
  • 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-manager-rotation-command.test.ts
  • test/app-bind.test.ts
  • test/runtime-current-account.test.ts
  • test/codex-bin-wrapper.test.ts
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/storage-paths.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/privacy.md
lib/**/*.ts

📄 CodeRabbit inference engine (lib/AGENTS.md)

lib/**/*.ts: Route all public exports through lib/index.ts or documented package subpaths.
Keep module dependencies acyclic and preserve the layering types/constants → storage → accounts → runtime → manager/CLI; lower layers must not import higher layers.
Preserve runtime rotation pass-through semantics except for intentionally changed auth or provider headers.
Deduplicate emails using normalizeEmailKey(), which trims and lowercases the email.
Use classes for state requiring multiple independent instances or dependency injection, including AccountManager, CircuitBreaker, SessionAffinityStore, and the CodexError hierarchy. Reserve module-level state for genuinely process-global concerns and provide a test reset helper for such state.
Never import from dist/ in source tests or library code.
Never suppress type errors.
Never patch official Codex application binaries for desktop routing.
Never use bare recursive cleanup in Windows-sensitive paths without retry handling.

Files:

  • lib/codex-manager/commands/rotation.ts
  • lib/runtime/runtime-current-account.ts
  • lib/runtime/app-bind.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/commands/rotation.ts
  • lib/runtime/runtime-current-account.ts
  • lib/runtime/app-bind.ts
lib/{runtime-rotation-proxy.ts,runtime/**/*.ts}

📄 CodeRabbit inference engine (lib/AGENTS.md)

Runtime rotation must fail open to normal official Codex forwarding when startup helpers are unavailable.

Files:

  • lib/runtime/runtime-current-account.ts
  • lib/runtime/app-bind.ts
lib/runtime/**/*.{ts,js}

📄 CodeRabbit inference engine (AGENTS.md)

Do not patch official Codex app binaries; use the reversible app-bind or launcher-helper mechanisms instead.

Files:

  • lib/runtime/runtime-current-account.ts
  • lib/runtime/app-bind.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
scripts/codex.js

📄 CodeRabbit inference engine (AGENTS.md)

Keep codex-multi-auth-codex auth commands local, but forward non-auth commands to the official Codex CLI; do not reimplement general Codex commands.

Files:

  • scripts/codex.js
scripts/**/*.{ts,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-11T18:05:55.408Z
Learning: Runtime configuration source precedence must be: existing `CODEX_MULTI_AUTH_CONFIG_PATH`, valid unified-settings `pluginConfig`, legacy compatibility files, then `DEFAULT_PLUGIN_CONFIG`; environment variables override individual runtime settings afterward.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:05:55.408Z
Learning: A set-but-missing `CODEX_MULTI_AUTH_CONFIG_PATH` must be ignored during loading but remain the save target when the environment variable is set.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:05:55.408Z
Learning: When `CODEX_HOME` is non-default, multi-auth must resolve strictly to `$CODEX_HOME/multi-auth` and must not scan other roots for existing account pools.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:05:55.408Z
Learning: The per-invocation account pin from `--account` or `CODEX_MULTI_AUTH_FORCE_ACCOUNT` must be ephemeral, take precedence over the environment variable, never modify the persisted switch pin, and fail hard when the runtime rotation proxy is disabled.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:05:55.408Z
Learning: The runtime rotation proxy must preserve request bodies and streaming responses, replace outbound authorization with the selected managed account, remove hop-by-hop/private metadata headers and stale decoded `content-encoding`, and return a structured pool-exhaustion error when no account is available.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:05:55.408Z
Learning: OAuth token revocation responses must be returned directly instead of rotating to another account; the affected account receives the configured token-invalidation cooldown.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:05:55.408Z
Learning: Package install scripts must remain side-effect-free: postinstall may print a short notice but must not perform setup, npm installation, or update commands.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:05:55.408Z
Learning: First-run desktop self-heal must run only for durable global installs, with setup state recorded at `~/.codex/multi-auth/first-run-setup.json`; `npx` and project-local installs must not consume the marker.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:05:55.408Z
Learning: The wrapper may perform a best-effort daily npm version check, but it must only print a manual upgrade notice and must never execute npm install or update commands automatically.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:05:55.408Z
Learning: Persisted or rewritten Codex configuration must use `cli_auth_credentials_store = "file"` unless explicitly opted out through `CODEX_MULTI_AUTH_ENFORCE_CLI_FILE_AUTH_STORE=0`.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:06:24.959Z
Learning: Persist canonical settings in `~/.codex/multi-auth/settings.json` with top-level `version`, `dashboardDisplaySettings`, and `pluginConfig` fields.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:06:24.959Z
Learning: Keep runtime rotation controls separated by layer: persisted settings, per-process overrides, wrapper app-helper environment, and packaged app-bind environment.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:06:24.959Z
Learning: Treat backup/WAL artifacts created during storage writes and recovery as normal temporary safety behavior.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:06:31.830Z
Learning: Data handling must comply with OpenAI's Terms of Use and Privacy Policy.
📚 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-manager-rotation-command.test.ts
  • test/app-bind.test.ts
  • test/runtime-current-account.test.ts
  • 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-manager-rotation-command.test.ts
  • test/app-bind.test.ts
  • test/runtime-current-account.test.ts
  • test/codex-bin-wrapper.test.ts
🪛 ast-grep (0.45.1)
test/runtime-current-account.test.ts

[warning] 547-557: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFile(
join(tempDir, runtime-rotation-app-helper.${process.pid}.json),
JSON.stringify({
kind: "codex-app-runtime-rotation-helper",
state: "running",
pid: process.pid,
lastAccountId: "acc_live",
updatedAt: now - 30_000,
}),
"utf8",
)
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)


[warning] 574-584: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFile(
join(tempDir, "runtime-rotation-app-helper.99999998.json"),
JSON.stringify({
kind: "codex-app-runtime-rotation-helper",
state: "idle-timeout",
pid: 99999998,
lastAccountId: "acc_older",
updatedAt: now - 60_000,
}),
"utf8",
)
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)


[warning] 585-595: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFile(
join(tempDir, "runtime-rotation-app-helper.99999999.json"),
JSON.stringify({
kind: "codex-app-runtime-rotation-helper",
state: "stopped",
pid: 99999999,
lastAccountId: "acc_newer",
updatedAt: now - 10_000,
}),
"utf8",
)
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)

lib/codex-manager/commands/rotation.ts

[warning] 557-560: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(
^${basePattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.\\d+\\.json$,
"i",
)
Note: [CWE-1333] Inefficient Regular Expression Complexity

(regexp-from-variable)

lib/runtime/runtime-current-account.ts

[warning] 160-163: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(
^${basePattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.\\d+\\.json$,
"i",
)
Note: [CWE-1333] Inefficient Regular Expression Complexity

(regexp-from-variable)

lib/runtime/app-bind.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 { spawn } 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 { spawn } 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] 1513-1516: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(
^${helperStatusPrefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.\\d+\\.json$,
"i",
)
Note: [CWE-1333] Inefficient Regular Expression Complexity

(regexp-from-variable)


[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 { spawn } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

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)


[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)

scripts/codex.js

[warning] 4043-4046: Detects non-literal values in regular expressions
Context: new RegExp(
^${baseName.replace(/\.json$/i, "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.(\\d+)\\.json$,
"i",
)
Note: [CWE-1333] Inefficient Regular Expression Complexity (ReDoS via non-literal RegExp).

(detect-non-literal-regexp)

🪛 OpenGrep (1.26.0)
scripts/codex.js

[ERROR] 4079-4079: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)


[ERROR] 4079-4079: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)

🔇 Additional comments (30)
scripts/codex.js (10)

3-3: LGTM!


88-100: LGTM!


3792-3806: LGTM!


3934-3969: LGTM!


4034-4082: LGTM!

Also applies to: 4091-4108


4188-4232: LGTM!


4255-4259: LGTM!


4332-4335: LGTM!


4348-4360: LGTM!


4450-4474: LGTM!

lib/runtime/runtime-current-account.ts (3)

1-1: LGTM!

Also applies to: 127-129


156-176: LGTM!


178-198: LGTM!

lib/codex-manager/commands/rotation.ts (2)

1-1: LGTM!

Also applies to: 518-520, 550-575


650-655: LGTM!

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

692-704: LGTM!

Also applies to: 3084-3090


2848-2870: LGTM!

Also applies to: 3598-3602


3160-3236: LGTM!


3311-3343: LGTM!

Also applies to: 3354-3363


3344-3353: 🎯 Functional Correctness

remove the parse-error concern. test/codex-bin-wrapper.test.ts:3346 and test/codex-bin-wrapper.test.ts:3349 each contain one valid type assertion.

			> Likely an incorrect or invalid review comment.
test/codex-manager-rotation-command.test.ts (1)

449-504: LGTM!

test/runtime-current-account.test.ts (1)

545-598: LGTM!

docs/reference/storage-paths.md (1)

42-42: LGTM!

docs/configuration.md (1)

76-76: LGTM!

docs/development/CONFIG_FIELDS.md (1)

270-272: LGTM!

docs/development/ARCHITECTURE.md (1)

200-200: LGTM!

lib/runtime/app-bind.ts (2)

4-4: LGTM!

Also applies to: 1648-1648


1530-1599: the per-helper decision logic reads correctly; identity gating holds.

traced each branch: the legacy exact name cannot match the \.\d+\.json pattern, so no candidate is processed twice. a set identityToken with a missing or mismatched owner file preserves the status instead of signalling a PID this process cannot prove it owns. the alive-then-stop sequence at lib/runtime/app-bind.ts:1555 and lib/runtime/app-bind.ts:1565 is TOCTOU-safe because stopRuntimeRotationAppHelperProcess re-verifies start time, arg, and scriptPath at lib/runtime/app-bind.ts:1288-1310 before it signals.

one gap: test/app-bind.test.ts covers only the dead per-PID helper. see the comment on that file for the missing cases.

AGENTS.md (1)

139-139: LGTM!

docs/privacy.md (1)

33-33: LGTM!

Also applies to: 91-91, 118-118

Comment thread docs/development/ARCHITECTURE.md
Comment thread docs/reference/storage-paths.md Outdated
Comment thread lib/codex-manager/commands/rotation.ts Outdated
Comment thread lib/runtime/app-bind.ts Outdated
Comment thread lib/runtime/app-bind.ts Outdated
Comment thread scripts/codex.js Outdated
Comment thread scripts/codex.js
Comment thread test/app-bind.test.ts
Comment thread test/codex-bin-wrapper.test.ts
Comment thread test/codex-bin-wrapper.test.ts
Shared per-PID status discovery moves next to its filename constant
(listRuntimeHelperStatusPaths) and all three readers use it; rotation
status derives selection and live count from one scan; only "running"
counts as running so max-lifetime/error stamps read as terminal. The
helper's identity probe is async and single-flight so a wedged ps stalls
a background probe, never the proxy event loop. Metadata deletions retry
transient Windows locks; the launch-path sweep memoizes identity probes
per PID and caps them per sweep. app-bind unbind logs when it cannot
enumerate per-PID files and retries the readdir. Tests: multi-helper and
ownership-preservation unbind cases, publish-rate regression, sweep
retry regression, max-lifetime status case, POSIX gate plus a Windows
companion for the identity-unavailable degradation.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01FW2tPyLdcRXrnGsYVVJeEj
@possibilities

Copy link
Copy Markdown
Contributor Author

All twelve findings triaged — eleven accepted and fixed, one declined with reasoning. Pushed as f5bf873.

✅ accepted

  • max-lifetime record read as running (outside-diff, rotation.ts) — correct and the sharpest catch in the round: a terminal stamp plus a recycled PID would have resurrected exactly the gate this PR stops trusting. Only "running" is running now, matching the other reader, with a regression case for a max-lifetime record whose PID is alive.
  • helper-side execFileSync blocks the proxy event loop — the recheck is now an async, single-flight probe: the tick always answers from the last verdict, the probe updates it in the background, and a wedged ps holds one unref'd child, not one per tick. Launcher/sweep stay synchronous as you suggested. Mutation-reverified: stubbing the shared parse to null still flips the identity-mismatch test to failing through the async path.
  • shared per-PID discoverylistRuntimeHelperStatusPaths lives next to the filename constant in lib/runtime-constants.ts (your layering note was right) and all three readers use it; there were three copies, not two.
  • rotation status double scan — one scan now feeds selection, the live count, and the current-account signal, so the line cannot pair one instant's helper with another instant's count.
  • metadata deletions retry Windows locks — all three rmSync sites route through the existing synchronous retry, with a simulated-EBUSY regression test (two injected failures, file still removed).
  • sweep probe bound — identity probes are memoized per PID and capped per sweep (remainder treated as not-dead; the next launch finishes). On the declined half of this finding, see below.
  • app-bind readdir — retried via withFileOperationRetry and warned on non-ENOENT failure instead of silently degrading to legacy-only cleanup.
  • plural unbind coverage — added: two dead per-PID files plus the legacy file removed in one unbind (owner file asserted too), and the ownership-preservation case — a live PID with an identityToken and no owner file survives with the warning logged. No follow-up issue needed; both cases are in this PR.
  • POSIX gate on the identity-mismatch test — gated, with a Windows companion asserting the designed degradation (identity unavailable + live owner PID ⇒ helper stays alive). Note the Windows CI job currently runs typecheck only, so the companion protects a Windows developer running the suite locally rather than CI.
  • publish-rate regression — a quiet helper's status file mtime is asserted unchanged across several ticks.
  • docs — owner-file row added to the ARCHITECTURE storage table; retention rule (terminal stamps persist until the next launch's sweep; owner files removed on clean exit) added to storage-paths.

❌ declined: seeding hundreds of stale files to test sweep boundedness

The 579-file case that motivates the concern never probes ps at all — those PIDs are dead, and dead PIDs short-circuit before any identity read. Probes happen only for candidates whose PID is currently alive, which is bounded by real concurrent processes, and now additionally memoized and capped at 20 per sweep, so the worst case is structurally constant rather than empirically tested. A hundreds-of-files fixture would exercise the dead-PID path the existing sweep test already covers.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@test/codex-bin-wrapper.test.ts`:
- Around line 3436-3467: Update the retry test around
withSynchronousFileOperationRetry to explicitly document its four-attempt
contract and that two injected EBUSY failures require the third deletion
attempt. Add a deterministic Windows regression scenario covering two stale
metadata files while accounting for the process-wide failure counter, ensuring
cleanup succeeds consistently regardless of file ordering.
🪄 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: b9ce91e6-d362-481c-b6ab-957f0b4371ee

📥 Commits

Reviewing files that changed from the base of the PR and between 52d92c1 and f5bf873.

📒 Files selected for processing (10)
  • docs/development/ARCHITECTURE.md
  • docs/reference/storage-paths.md
  • lib/codex-manager/commands/rotation.ts
  • lib/runtime-constants.ts
  • lib/runtime/app-bind.ts
  • lib/runtime/runtime-current-account.ts
  • scripts/codex.js
  • test/app-bind.test.ts
  • test/codex-bin-wrapper.test.ts
  • test/codex-manager-rotation-command.test.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (22)
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/development/ARCHITECTURE.md
  • docs/reference/storage-paths.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
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/development/ARCHITECTURE.md
  • docs/reference/storage-paths.md
docs/**/*

📄 CodeRabbit inference engine (docs/configuration.md)

docs/**/*: Keep the recommended defaults enabled for menu auto-fetch limits, menu sorting, live account synchronization, session affinity, proactive refresh guarding, and preemptive quota handling.
Validate effective configuration using codex-multi-auth status, list, check, and forecast --live when reviewing or troubleshooting configuration changes.

Files:

  • docs/development/ARCHITECTURE.md
  • docs/reference/storage-paths.md
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/development/ARCHITECTURE.md
  • docs/reference/storage-paths.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-manager-rotation-command.test.ts
  • test/app-bind.test.ts
  • 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-manager-rotation-command.test.ts
  • lib/runtime-constants.ts
  • lib/runtime/app-bind.ts
  • test/app-bind.test.ts
  • lib/codex-manager/commands/rotation.ts
  • scripts/codex.js
  • lib/runtime/runtime-current-account.ts
  • test/codex-bin-wrapper.test.ts
**/*.{ts,js}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,js}: Use ESM-only modules; the package is configured with "type": "module" and targets Node.js >= 18.17.
Do not use as any, @ts-ignore, or @ts-expect-error.

Files:

  • test/codex-manager-rotation-command.test.ts
  • lib/runtime-constants.ts
  • lib/runtime/app-bind.ts
  • test/app-bind.test.ts
  • lib/codex-manager/commands/rotation.ts
  • scripts/codex.js
  • lib/runtime/runtime-current-account.ts
  • test/codex-bin-wrapper.test.ts
test/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Windows-sensitive tests and cleanup helpers must exercise retry handling for transient filesystem locks and failures.

Files:

  • test/codex-manager-rotation-command.test.ts
  • test/app-bind.test.ts
  • 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-manager-rotation-command.test.ts
  • test/app-bind.test.ts
  • test/codex-bin-wrapper.test.ts
lib/**/*.ts

📄 CodeRabbit inference engine (lib/AGENTS.md)

lib/**/*.ts: Route all public exports through lib/index.ts or documented package subpaths.
Keep module dependencies acyclic and preserve the layering types/constants → storage → accounts → runtime → manager/CLI; lower layers must not import higher layers.
Preserve runtime rotation pass-through semantics except for intentionally changed auth or provider headers.
Deduplicate emails using normalizeEmailKey(), which trims and lowercases the email.
Use classes for state requiring multiple independent instances or dependency injection, including AccountManager, CircuitBreaker, SessionAffinityStore, and the CodexError hierarchy. Reserve module-level state for genuinely process-global concerns and provide a test reset helper for such state.
Never import from dist/ in source tests or library code.
Never suppress type errors.
Never patch official Codex application binaries for desktop routing.
Never use bare recursive cleanup in Windows-sensitive paths without retry handling.

Files:

  • lib/runtime-constants.ts
  • lib/runtime/app-bind.ts
  • lib/codex-manager/commands/rotation.ts
  • lib/runtime/runtime-current-account.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/runtime-constants.ts
  • lib/runtime/app-bind.ts
  • lib/codex-manager/commands/rotation.ts
  • lib/runtime/runtime-current-account.ts
lib/{runtime-rotation-proxy.ts,runtime/**/*.ts}

📄 CodeRabbit inference engine (lib/AGENTS.md)

Runtime rotation must fail open to normal official Codex forwarding when startup helpers are unavailable.

Files:

  • lib/runtime/app-bind.ts
  • lib/runtime/runtime-current-account.ts
lib/runtime/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Keep runtime rotation default-on through codexRuntimeRotationProxy, while preserving the documented opt-out behavior.

Files:

  • lib/runtime/app-bind.ts
  • lib/runtime/runtime-current-account.ts
lib/runtime/app-bind.ts

📄 CodeRabbit inference engine (AGENTS.md)

Do not patch official Codex app binaries; use the reversible app-bind or launcher-helper mechanism instead.

Files:

  • lib/runtime/app-bind.ts
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/storage-paths.md
scripts/codex*.js

📄 CodeRabbit inference engine (AGENTS.md)

Do not bypass the official Codex CLI by reimplementing general Codex commands in the wrapper; local handling is limited to account/auth commands and other commands must be forwarded.

Files:

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

📄 CodeRabbit inference engine (AGENTS.md)

Windows-sensitive cleanup and writes must retry transient EBUSY, EPERM, and ENOTEMPTY failures where applicable; avoid bare recursive deletion.

Files:

  • scripts/codex.js
scripts/codex.js

📄 CodeRabbit inference engine (AGENTS.md)

Shadow CODEX_HOME handling must preserve official Codex state, synchronize state back safely, and clean up locks and temporary provider configuration.

Files:

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

Timestamp: 2026-08-11T18:28:06.380Z
Learning: Package installation scripts must remain side-effect-free; first-run durable CLI setup performs best-effort repair without blocking the requested command.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:28:06.380Z
Learning: Budget guards are intentionally soft under concurrency because evaluations use a pre-request ledger snapshot; concurrent requests may briefly overshoot.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:28:23.215Z
Learning: Store account and session state locally under the configured runtime root; honor `CODEX_MULTI_AUTH_DIR` and `CODEX_MULTI_AUTH_CONFIG_PATH` overrides.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:28:23.215Z
Learning: Do not add custom analytics, a project-owned remote database, or network calls outside required OAuth, backend/update, and listed GitHub endpoints.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:28:23.215Z
Learning: Keep runtime rotation and the optional local bridge loopback-only. The bridge must expose only `/health`, `/v1/models`, and `/v1/responses`, and require a local bearer token by default.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:28:23.215Z
Learning: Store local bridge client tokens as SHA-256 hashes rather than plaintext; show plaintext tokens only during creation or rotation.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:28:23.215Z
Learning: Do not log prompts, authorization headers, raw sensitive account identifiers, or other sensitive payloads in usage and observability metadata.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:28:23.215Z
Learning: Treat raw request and response body logs enabled by `CODEX_PLUGIN_LOG_BODIES=1` as sensitive data, and rotate or delete them as needed.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:28:23.215Z
Learning: Ensure cleanup removes all multi-auth-owned data, including accounts, caches, leases, usage, backups, projects, app-bind state, logs, prompt caches, helper files, and configured override-root paths.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:28:23.215Z
Learning: Use hashed email/account identity values where usage or account-policy metadata requires identity keys; do not persist raw sensitive account identifiers.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:28:23.215Z
Learning: Keep local request metadata summaries free of prompts, authorization headers, and raw sensitive account IDs.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:28:23.215Z
Learning: Ensure account pools, policies, routing profiles, budget guards, backups, and runtime metadata remain local to the configured runtime root.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:28:23.215Z
Learning: Usage of the project must comply with OpenAI’s Terms of Use and Privacy Policy.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:28:33.293Z
Learning: When `CODEX_HOME` is set to a non-default directory, resolve multi-auth storage strictly under `$CODEX_HOME/multi-auth` and do not scan the default `~/.codex/multi-auth` directory.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:28:33.293Z
Learning: Treat `~/.codex/multi-auth` as project-owned storage, while `~/.codex/accounts.json`, `~/.codex/auth.json`, and `~/.codex/config.toml` remain official Codex CLI-owned files.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:28:33.293Z
Learning: Never read from or write to the OS keychain or the `security` CLI; use the official Codex file-backed auth layout instead.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:28:33.293Z
Learning: Reconcile the persisted top-level `cli_auth_credentials_store` value in `config.toml` to `"file"` during first-run setup, wrapper startup, and `doctor --fix`, while leaving profile-level assignments unchanged.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:28:33.293Z
Learning: Preserve existing `config.toml` line endings and accept either TOML string form when recognizing `cli_auth_credentials_store = 'file'` or `"file"`; insert a missing top-level key before the first table.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:28:33.293Z
Learning: Make first-run setup one-shot and concurrency-safe using an exclusive marker create; failures must be debug-logged without blocking the user command, and pre-v2 or unreadable markers must migrate by replaying only the auth-store step.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:28:33.293Z
Learning: Use atomic writes and appropriate Windows `EPERM`/`EBUSY` retry handling for configuration and storage updates; if wrapper-startup reconciliation ultimately fails, swallow the error and continue because the per-invocation override protects that run.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:28:33.293Z
Learning: Do not take locks for concurrent interactive TUI sessions; they operate directly on the canonical `CODEX_HOME` and must preserve stock concurrent-session behavior.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:28:33.293Z
Learning: Exclude cache-like artifacts and `.reset-intent` markers from recovery candidates; suppress flagged-account backup recovery while the flagged reset marker remains present.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:28:33.293Z
Learning: Named backup export names may contain only letters, numbers, `_`, and `-`; append `.json` when omitted, reject path separators, `..`, `.rotate.`, `.tmp`, and `.wal`, and do not overwrite existing files except through an explicit lower-level force path.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:28:33.293Z
Learning: Keep the local bridge loopback-only, expose only `/health`, `/v1/models`, and `/v1/responses`, and persist token hashes rather than plaintext tokens; show plaintext tokens only during explicit create or rotate commands.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:28:33.293Z
Learning: Enforce account pause and drain policies at selection time through `evaluateRuntimePolicy`, excluding blocked accounts from hybrid rotation.
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-08-11T18:28:33.293Z
Learning: Run `npm run build` and the targeted unified-settings, storage-recovery-paths, and storage-flagged tests when validating backup or restore changes.
📚 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-manager-rotation-command.test.ts
  • test/app-bind.test.ts
  • 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-manager-rotation-command.test.ts
  • test/app-bind.test.ts
  • test/codex-bin-wrapper.test.ts
🪛 ast-grep (0.45.1)
lib/runtime-constants.ts

[warning] 26-29: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(
^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.\\d+\\.json$,
"i",
)
Note: [CWE-1333] Inefficient Regular Expression Complexity

(regexp-from-variable)

lib/runtime/app-bind.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 { spawn } 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 { spawn } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

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 (11)
scripts/codex.js (1)

3899-3954: LGTM!

Also applies to: 3974-4013, 4060-4095, 4104-4191, 4318-4345

docs/development/ARCHITECTURE.md (1)

279-279: LGTM!

lib/runtime-constants.ts (1)

1-1: LGTM!

Also applies to: 12-37

lib/runtime/runtime-current-account.ts (1)

5-5: LGTM!

Also applies to: 156-166

lib/codex-manager/commands/rotation.ts (1)

33-33: LGTM!

Also applies to: 549-591, 623-651, 705-714, 737-739

lib/runtime/app-bind.ts (2)

13-13: LGTM!

Also applies to: 1510-1533


1652-1652: 🩺 Stability & Availability

remove this concern; unlinkIfExists already retries windows lock errors. lib/runtime/app-bind.ts:302 wraps unlink(path) with withFileOperationRetry, and test/app-bind-io-retry.test.ts:182 covers transient cleanup failures.

			> Likely an incorrect or invalid review comment.
test/app-bind.test.ts (1)

1168-1222: LGTM!

Also applies to: 1224-1267

docs/reference/storage-paths.md (1)

162-162: LGTM!

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

3245-3247: LGTM!

Also applies to: 3278-3310, 3401-3431

test/codex-manager-rotation-command.test.ts (1)

506-530: LGTM!

Comment thread test/codex-bin-wrapper.test.ts
@possibilities

Copy link
Copy Markdown
Contributor Author

Accepted — pushed as 1590cd1. The test now seeds two stale files so the process-wide failure counter's distribution across deletions cannot matter, and the comment documents the dependency: four attempts per call means the worst split (one file eating both simulated failures) still succeeds on that file's third attempt, and a retry budget below three flips this test red.

ndycode added a commit that referenced this pull request Aug 13, 2026
Two of these were documentation this PR had itself made wrong: moving the
metadata sweep after `spawn()` invalidated a claim in ARCHITECTURE.md and
a comment in the wrapper test that both still said "before".

Behaviour:

- `ps` does not exist on Windows, so `readProcessStartTimeMs` and its
  async twin could only ever fail there — once per launcher launch and up
  to `probeBudget` times per sweep, each one a process spawn that learns
  nothing. Both short-circuit on win32 now. Windows runs owner liveness
  on bare `kill(pid, 0)` and the 24h ceiling is what bounds a leak there;
  the "degraded check" row says so instead of implying it is rare.

- The owner-identity recheck was pinned at 60s. `lastIdentityVerdict`
  starts optimistic, so the first tick reports the owner alive while the
  probe is in flight — deliberate against a 12h timeout, but the
  lifecycle tests compress the window to 250ms, where a 60s recheck is
  longer than the whole thing under test and the flip came down to probe
  timing. The interval now scales off the resolved idle/detached window.
  Production is unchanged: both defaults are hours.

- `mapWithConcurrency` retired a runner on an `undefined` item rather
  than skipping it. Unreachable today — `items` is `string[]` — but the
  failure mode it guards is "helpers left running while the user is told
  the app was unbound", so only running past the end ends a runner.

- The orphan owner pass preserved a live-PID owner file without a word,
  while every other preserve in that function warns. Telling "a helper is
  starting right now" from "the PID was recycled" needs the
  recorded-start-time comparison the launcher sweep does and unbind has
  no equivalent of; that stays a scope decision, but not a silent one.

Fixtures:

- Windows allocates PIDs from a pool rather than a monotonic counter, so
  `withDeadPid`'s "a just-exited PID is not reused" did not hold there —
  and its callers assert dead-PID cleanup on every platform. Deadness is
  re-asserted immediately before the PID is handed over, turning a rare
  Windows-only flake in a cleanup test into an immediate fixture error.
- The parent end of the stdin pipe is destroyed on reap; `exit` fires
  before stdio teardown and some fixtures hold 16 at once.
- The hand-rolled spawn/SIGKILL/poll copy in the wrapper test uses
  `withDeadPid`, which waits on `exit` instead of polling.
- The EPERM owner-liveness test is win32-skipped: it sources the owner
  start time from `ps`, so on Windows the env var was empty, the identity
  branch never engaged, and it exercised bare liveness under a name
  claiming otherwise.
- Nested `withDeadPid` scopes flattened via `withDeadPids`.

Coverage:

- `UNBIND_HELPER_CONCURRENCY` is exported and observed. With three
  records any pool width behaved identically, so an edit to `Infinity`
  would have shipped green; a fixture now runs 2x the bound in live
  helper records through unbind and measures peak in-flight at the
  `verifyProcessIdentity` seam.
- test/app-helper-selection.test.ts covers the four selector predicates
  directly — non-positive/fractional PIDs, every terminal state, the
  staleness boundary either side by 1ms, null `updatedAt`, `startedAt`
  inside and outside the clock tolerance, recency in both input orders.
- The staleness window is pinned to the wrapper's heartbeat. The wrapper
  cannot import from `lib/`, so nothing linked the two numbers; the test
  reads the constant out of `scripts/codex.js` and asserts ten heartbeats
  still fit inside the window.
- A permanently locked metadata file is asserted survivable rather than
  assumed: the launch still exits 0 and the file waits for the next sweep.

Not taken: a cache for the synchronous helper-status scan. It is
pre-existing (#664 introduced the per-PID scan) and unchanged here; the
menu loop blocks on user input between iterations, and the accumulation
that would make it hurt is what this PR bounds. A time-based cache would
show stale account state in the UI it is meant to speed up.

Still uncovered: the mtime guard's negative path — a file replaced
between classification and deletion. Forcing a write into that window
needs another production test hook, which is too high a price for a
microseconds-wide race.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_015b4Lew3oHNEYmTZtg7zEWz
@ndycode
ndycode merged commit 1590cd1 into ndycode:main Aug 13, 2026
2 checks passed
ResponseIV pushed a commit to ResponseIV/codex-multi-auth that referenced this pull request Aug 13, 2026
The detached reap added in ndycode#665 could kill a live `codex app` session.

`codex app` relies on the detach grace rather than an explicit
`detachOnExit`, so its launcher exits inside the grace window and the
helper's owner is dead from the first tick. From then on the only thing
standing between the desktop app and a dead proxy was
`countOpenConnections() === 0` — and the proxy never sets
`server.keepAliveTimeout`, so Node closes idle client sockets after its
5s default. A user who stops typing for the length of the detached
window has zero sockets and no new requests, so the helper exits
`owner-gone`, and the next message gets ECONNREFUSED against a dead
localhost port with nothing left to restart it. Pre-ndycode#665 that session
survived for the full 12h idle timeout.

Gate the reap on the helper having *never* served a request. Every
leaked helper in the ndycode#663 report had `totalRequests: 0`, so the leak is
entirely a never-served phenomenon and the narrower gate closes it in
full; a helper that served anything was genuinely handed off and falls
back to the idle timeout and the 24h lifetime ceiling, which is where it
sat before the detached window existed.

Two more lifecycle fixes in the same tick:

- The owner verdict is now three-valued. "No owner PID was recorded" and
  "the owner is confirmed dead" are different facts, and collapsing them
  into one `false` started the detached clock on the first tick for any
  helper launched without an owner PID — invoked directly, which is the
  documented reproduction in ndycode#663, or spawned by a pre-upgrade launcher —
  and reaped it silently 15 minutes later. `unknown` fires neither
  branch, which is what the pre-ndycode#664 `ownerPid && isAlive(ownerPid)`
  guard did.

- The status heartbeat now accounts for the detached window.
  `publishToken` zeroes `idleExpiresAt`, so the published deadline only
  catches up on a heartbeat; pinned to the idle window alone, `rotation
  status` kept advertising a 12h deadline for a helper seconds from
  exiting, and under a short DETACHED_IDLE_MS override it never caught up
  at all.

Also in this commit, both from the same review pass:

- The metadata sweep runs after the helper spawn instead of before it.
  It is synchronous and unbounded — readdir, a readFileSync per live
  candidate, rmSync with a blocking backoff, bounded `ps` probes — and
  the state it cleans up is exactly the state that makes it slow, so it
  sat in front of `codex app` and TUI startup. Nothing about spawning
  depends on it. The launch timeout is armed after it either way.

- Sweep deletions are guarded by an mtime re-check. Classifying a file as
  stale and deleting it are two moments, and a PID freed between them can
  be handed to a helper starting right now, which republishes that exact
  path before the delete lands.

- The published wrapper's fault injectors need an explicit
  CODEX_MULTI_AUTH_TEST_FAULT_INJECTION=1 opt-in and a strict digits-only
  parse. `Number.parseInt` reads "2abc" as 2 and "1e3" as 1, so a value
  that was never meant to be a count could arm an injector in a user's
  install and silently defeat the first N metadata deletions of every
  sweep (ndycode#668).

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_015b4Lew3oHNEYmTZtg7zEWz
ResponseIV pushed a commit to ResponseIV/codex-multi-auth that referenced this pull request Aug 13, 2026
ndycode#664 moved helper status files to `runtime-rotation-app-helper.<pid>.json`
and updated `docs/reference/storage-paths.md`, but the storage table in
the README kept the pre-per-PID shared name.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_015b4Lew3oHNEYmTZtg7zEWz
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] runtime rotation app helpers leak past their idle timeout; all helpers trample one shared status file

2 participants