Skip to content

feat: add mcodex launcher statusline#500

Merged
ndycode merged 6 commits into
ndycode:mainfrom
oranmizrahi:feat/mcodex-statusline
Jun 2, 2026
Merged

feat: add mcodex launcher statusline#500
ndycode merged 6 commits into
ndycode:mainfrom
oranmizrahi:feat/mcodex-statusline

Conversation

@oranmizrahi
Copy link
Copy Markdown
Contributor

@oranmizrahi oranmizrahi commented Jun 1, 2026

PR Summary

Add a lightweight mcodex launcher and cached multi-auth status display for Codex sessions.

What Changed

  • Added optional tmux launch mode:
mcodex --tmux
  • Added opt-in live account monitor pane:
mcodex --tmux --live-accounts
  • Added monitor-only mode:
mcodex --monitor
  • Added cached startup status line before Codex launches:
codex-multi-auth | gpt-5.5 medium | ~ | Account 3 | 5h 1% 14:25 | week 16% Jun 7 | user@example.com(Plus) | cache 10m

Cache Behavior

  • Reads local quota-cache.json, runtime-observability.json, and account storage.
  • Does not call OpenAI on every launch.
  • Refreshes quota data in the background only when cache is stale.
  • Default refresh interval: 10 minutes.
  • Uses a lock to avoid multiple concurrent refreshes.
  • Stale refresh locks are recovered after 10 minutes.

Controls

CODEX_MULTI_AUTH_STATUSLINE=0 mcodex
CODEX_MULTI_AUTH_STATUS_QUOTA_REFRESH_INTERVAL_MS=1800000 mcodex
MCODEX_MONITOR_INTERVAL=10 mcodex --tmux --live-accounts

Validation

  • Build passed.
  • Script lint passed.
  • mcodex syntax check passed.
  • Verified wrapper still uses:
provider: codex-multi-auth-runtime-proxy

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

Greptile Summary

adds a lightweight mcodex bash launcher with optional tmux session management and a cached startup status line (codex-multi-auth | model | cwd | Account N | usage | email(plan) | cache age) emitted to stderr before codex launches.

  • scripts/mcodex: new entrypoint with --tmux, --live-accounts, and --monitor modes; numeric-only validation on MCODEX_MONITOR_INTERVAL closes the shell-injection path flagged in review; args passed to codex are shell-quoted via printf %q.
  • scripts/codex.js: ~480 lines of new helpers for status-line formatting, per-project account pool resolution (via dist dynamic imports), and a background quota-cache refresh child with an async lock mechanism; previous issues around unix-only path sep, global-only account reads, and blocking event-loop callbacks are resolved in this diff.
  • test files: mcodex-launcher.test.ts drives the interval-validation regex directly; mcodex-statusline-scope.test.ts proves per-project vs. global pool routing end-to-end (guarded by hasDistBuild()); documentation.test.ts adds a publish-integrity check that every bin target is also in files[].

Confidence Score: 5/5

the core production paths in codex.js and mcodex are safe to merge; changes address previously-flagged injection, path-separator, and per-project account bugs, and gracefully degrade on any dist-import failure.

the status-line and background-refresh logic is well-isolated behind feature flags and TTY guards; no changed production path introduces new data loss, auth boundary crossings, or hard crashes. the remaining gaps are in the test layer and do not affect production behavior.

test/mcodex-statusline-scope.test.ts — the afterEach retry loop and dist/ import both warrant a second look before this lands on a windows CI runner

Important Files Changed

Filename Overview
scripts/codex.js adds ~480 lines of status-line logic: per-project accounts resolution via dist imports, quota-cache refresh with lock management, and async cleanup helpers; previous thread issues (blocking sleepSync, global-only accounts, path sep) are addressed in this diff
scripts/mcodex new bash launcher with tmux support, live-accounts monitor pane, and numeric validation for MCODEX_MONITOR_INTERVAL to prevent shell injection; previous thread injection concern is resolved by the validation block
test/mcodex-statusline-scope.test.ts end-to-end regression for per-project account scoping; violates test AGENTS.md by importing from dist/ and using a bare rmSync retry loop without inter-attempt sleep (Windows safety concern)
test/mcodex-launcher.test.ts new security regression suite for mcodex interval hardening; correctly skips on non-bash runners, exercises the shipped regex directly, and tests all injection vectors
test/documentation.test.ts extends bin-integrity check to assert every declared bin entry is also listed in files[], preventing a missing-shim publish regression
package.json adds mcodex to bin and files[]; straightforward and consistent with the existing bin declarations

Sequence Diagram

sequenceDiagram
    participant U as User
    participant M as mcodex (bash)
    participant C as codex.js (wrapper)
    participant D as dist/ helpers
    participant FS as ~/.codex/multi-auth/
    participant BG as background child (forecast)
    participant R as real codex binary

    U->>M: mcodex [args]
    M->>M: validate MCODEX_MONITOR_INTERVAL (numeric guard)
    alt --monitor
        M->>M: "run_monitor -> watch codex-multi-auth list"
    else --tmux
        M->>M: launch tmux session + optional live-accounts pane
        M->>C: exec codex-multi-auth-codex [args]
    else passthrough
        M->>C: exec codex-multi-auth-codex [args]
    end
    C->>D: resolveStatusAccountsDir() - import dist/lib/config.js, paths.js, state.js
    D-->>C: per-project or global accounts dir
    C->>FS: readJsonFileQuiet(accounts, quota-cache, runtime-observability)
    FS-->>C: storage, quotaCache, runtime data
    C->>C: "formatForwardStatusLine() -> build status string"
    C->>U: write status line to stderr
    C->>C: maybeRefreshQuotaCacheInBackground()
    C->>C: acquireStatusRefreshLock() - mkdirSync lock dir
    C->>BG: spawn codex-multi-auth forecast --live --json (detached+unref)
    BG-->>FS: writes updated quota-cache.json
    BG-->>C: "close/error -> removeDirectoryBestEffortAsync(lockDir)"
    C->>R: exec real codex binary [args]
Loading
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
test/mcodex-statusline-scope.test.ts:64-77
**`afterEach` retry loop has no sleep between attempts — ineffective on Windows**

the three-attempt `rmSync` loop fires synchronously with no delay between retries. on windows, `EBUSY`/`EPERM` on a just-exited child's directory can persist for tens of milliseconds, so all three attempts land within microseconds of each other and may all fail for the same reason. the test AGENTS.md anti-pattern explicitly says: "do not use bare `fs.rm` in test cleanup; use `removeWithRetry` for Windows safety." `removeWithRetry` uses `Atomics.wait`-backed backoff (20/60/120 ms) between attempts, giving the kernel time to release handles. using a plain tight retry loop risks leaving temp dirs behind on windows CI, which can cause subsequent test runs to fail if the same tmpdir is reused.

### Issue 2 of 3
test/mcodex-statusline-scope.test.ts:82-90
**`dist/` import in tests violates test AGENTS.md anti-pattern**

`projectAccountsDir` dynamically imports from `dist/lib/storage/paths.js`. the test AGENTS.md anti-pattern explicitly states: "do not rely on `dist/` in tests; use source files." if the dist tree is absent (e.g. a fresh checkout or a CI job that doesn't run `npm run build` before tests), `hasDistBuild()` returns `false` and the entire describe block is silently skipped — giving the false impression that the test suite passed when the regression coverage was never exercised. consider importing from the TypeScript source via `tsx`/`ts-node` transform, or at minimum add a comment explicitly acknowledging the exception so it's not accidentally widened.

### Issue 3 of 3
scripts/codex.js:205-445
**no unit vitest tests for the 20+ new helper functions**

the new status-line block adds many pure helpers (`formatCacheAge`, `formatPlan`, `formatUsageWindow`, `formatUsageSegment`, `resolveStatusAccountIndex`, `findAccountIndexByIdOrEmail`, `quotaCacheNeedsRefresh`, `extractConfigAssignmentValue`, `resolveStatusModel`, `formatStatusResetTime`, `formatStatusResetDate`, etc.) with zero direct unit tests. the only coverage is the two end-to-end tests in `mcodex-statusline-scope.test.ts`, which exercise the happy path but can't reach the edge cases (e.g. `usedPercent` out of `[0,100]`, `updatedAt <= 0`, empty/null plan strings, multi-entry quota cache where newest entry is < interval). the test AGENTS.md sets an 80% coverage threshold; these are easy-to-unit-test pure functions and missing vitest coverage here is a gap worth closing before the coverage gate catches it.

Reviews (5): Last reviewed commit: "fix(mcodex): gate monitor modes on `watc..." | Re-trigger Greptile

@oranmizrahi oranmizrahi requested a review from ndycode as a code owner June 1, 2026 07:00
@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
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

📝 Walkthrough

Walkthrough

this pr registers mcodex, adds a bash launcher with monitor and tmux modes, and updates lib/scripts/codex.js to emit a forward status line and refresh the quota-cache in background with locking.

Changes

codex forward status and mcodex entrypoint

Layer / File(s) Summary
status-line emission
lib/scripts/codex.js:205-579, lib/scripts/codex.js:4770-4771
parses persisted account/runtime/quota JSON, resolves account/model/reasoning, formats usage and plan, and conditionally emits a forward status line to stderr before forwarding execution.
quota-cache refresh & locking
lib/scripts/codex.js:21-24, lib/scripts/codex.js:85-87, lib/scripts/codex.js:170-191, lib/scripts/codex.js:580-683
computes refresh need from newest updatedAt, acquires a filesystem lock with stale-lock eviction, spawns a detached codex-multi-auth forecast --live --json child with suppressed status env, and schedules best-effort async lock removal using non-blocking rm helpers.
mcodex bash launcher (tmux & monitor)
lib/scripts/mcodex:1-108
new bash launcher. validates MCODEX_MONITOR_INTERVAL and MCODEX_TMUX_HISTORY_LIMIT, provides quote_args(), run_monitor(), configure_tmux_scrollback(), supports --monitor and --tmux/-t with optional --live-accounts, safely embeds quoted args into tmux, and falls back to exec codex-multi-auth-codex "$@".
mcodex launcher security tests
test/mcodex-launcher.test.ts:1-68
adds vitest suite that runs only when bash is available; validates interval sanitization (safe default fallback) and asserts the numeric guard regex is present in the shipped script.
package registration & doc test
lib/package.json:117, lib/package.json:132, test/documentation.test.ts:582
adds mcodex to package.json bin, includes scripts/mcodex in files, and updates the documentation test to expect the new bin entry.
statusline scope end-to-end tests
test/mcodex-statusline-scope.test.ts:1-190
adds conditional end-to-end tests that set up global and per-project account pools and assert statusline selects per-project or global pool depending on project presence and CLI sync.

Sequence Diagram(s)

sequenceDiagram
  participant main as lib/scripts/codex.js:main
  participant lockdir as quota_lock_dir
  participant child as codex_forecast_child
  main->>lockdir: check newest updatedAt -> decide refresh (lib/scripts/codex.js:580-683)
  main->>lockdir: acquire lock with stale eviction (lib/scripts/codex.js:580-683)
  main->>child: spawn detached forecast child (QUIET env) (lib/scripts/codex.js:580-683)
  child->>lockdir: write/update quota-cache files (forecast child)
  child->>main: exit -> triggers async lock cleanup (lib/scripts/codex.js:170-191)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

notes (lowercase, factual):

  • missing regression tests: no tests added for forward status line emission or the background quota-refresh/lock flow. reference: lib/scripts/codex.js:205-629. add unit/integration tests for display predicate and lock-refresh lifecycle.
  • concurrency risk: lock acquisition and stale-lock recovery require careful scrutiny for races when multiple processes detect stale locks and spawn children simultaneously. reference: lib/scripts/codex.js:580-683.
  • windows edge cases: scripts/mcodex is bash/tmux dependent and lacks windows fallbacks; verify behavior on non-unix shells. reference: lib/scripts/mcodex:1-108, test/mcodex-launcher.test.ts:1-68.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title follows conventional commit format with type 'feat', lowercase summary, and is well under 72 characters. Clearly describes the main change: adding the mcodex launcher with statusline.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed Pull request description is comprehensive, covering summary, changes, cache behavior, controls, and validation sections; all required template sections are present and mostly complete.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 and usage tips.

Comment thread scripts/codex.js
Comment thread scripts/mcodex
Comment thread scripts/codex.js
Comment thread scripts/codex.js
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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 `@scripts/codex.js`:
- Around line 557-592: The lock cleanup can be missed when the parent exits
immediately because maybeRefreshQuotaCacheInBackground spawns a detached child
(spawn options detached:true + child.unref()) and relies on parent-side
child.once('close'/'error') handlers to call removeDirectoryWithRetry(lockPath);
instead make the child process take explicit responsibility for lock cleanup by
passing the lockPath to the child (e.g., add CODEX_MULTI_AUTH_LOCK or similar to
the env passed into spawn) and update codex-multi-auth.js to call
removeDirectoryWithRetry(process.env.CODEX_MULTI_AUTH_LOCK) on startup and on
exit/uncaught errors (wrap in try/catch); keep the parent handlers as
best-effort fallback.

In `@scripts/mcodex`:
- Around line 17-24: The run_monitor function currently ignores any positional
parameters but the call if [[ "${1:-}" == "--monitor" ]]; then ... run_monitor
"$@" passes them, so either remove the unused "$@" from the call or make
run_monitor forward arguments to the watch command; fix by editing the
invocation in the conditional to call run_monitor without "$@" (or alternatively
modify run_monitor to accept "$@" and use them in the watched command, e.g.,
pass through to codex-multi-auth list) and ensure the symbol run_monitor and the
watch invocation around monitor_interval/codex-multi-auth list are updated
accordingly.
- Around line 7-15: The quote_args function currently appends a trailing space
via printf '%s ' "${quoted[@]}", which can inject an extra empty token when
interpolated into the tmux command; change quote_args to output the quoted
arguments without a trailing space (for example, print them joined without a
trailing delimiter or print each with a space except after the last) so that the
output from quote_args (used at lib/scripts/mcodex:52) has no trailing
whitespace; update the function name quote_args and its loop logic accordingly
to ensure no trailing space is emitted.
- Around line 51-52: The tmux invocation wraps the pre-quoted args ($args
produced by quote_args) inside a double-quoted command which can break when
arguments contain quotes or special chars; update the startup to avoid embedding
a pre-quoted command string into tmux. Instead either (a) create the session
without quoting the whole command and pass raw argv (use codex-multi-auth-codex
and the original "$@" or export a temporary env var and call
codex-multi-auth-codex reading that), or (b) create the session and then use
tmux send-keys to deliver a properly quoted/escaped command to the window (use
tmux send-keys with the literal arguments) so codex-multi-auth-codex receives
arguments safely; modify the code around args, quote_args, and the tmux
new-session call accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b2b78963-527f-4673-b5f9-978fa6c44539

📥 Commits

Reviewing files that changed from the base of the PR and between 70ab38a and de9ce86.

📒 Files selected for processing (3)
  • package.json
  • scripts/codex.js
  • scripts/mcodex
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (5)
package.json

📄 CodeRabbit inference engine (SECURITY.md)

package.json: Pin hono to version 4.12.14 or higher to avoid authentication bypass vulnerability in GHSA-xh87-mx6m-69f3
Pin rollup to version ^4.59.0 or higher to avoid vulnerable versions <4.59.0 in Vite and Vitest transitive dependencies

Files:

  • package.json
**/*.{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (AGENTS.md)

Use ESM only ("type": "module"), Node >= 18

Files:

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

📄 CodeRabbit inference engine (AGENTS.md)

Do not bypass the official Codex CLI by reimplementing general Codex commands in the wrapper

Files:

  • scripts/codex.js
{**/scripts/**/*.js,**/test/**/*.ts}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use bare recursive delete logic in Windows-sensitive scripts/tests without retry handling for transient EBUSY/EPERM/ENOTEMPTY errors

Files:

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

📄 CodeRabbit inference engine (AGENTS.md)

The package does not publish a global codex bin; codex-multi-auth-codex is the explicit wrapper

Files:

  • scripts/codex.js
🔇 Additional comments (30)
package.json (1)

117-117: LGTM!

Also applies to: 132-132

scripts/mcodex (1)

38-43: LGTM!

Also applies to: 61-61

scripts/codex.js (28)

84-86: LGTM!


182-189: LGTM!


191-207: LGTM!


209-213: LGTM!


215-235: LGTM!


237-245: LGTM!


247-293: LGTM!


295-302: LGTM!


304-312: LGTM!


314-326: LGTM!


328-349: LGTM!


351-358: LGTM!


360-366: LGTM!


368-376: LGTM!


378-385: LGTM!


387-394: LGTM!


396-404: LGTM!


406-418: LGTM!


420-430: LGTM!


432-437: LGTM!


439-444: LGTM!


446-455: LGTM!


457-487: LGTM!


489-494: LGTM!


496-502: LGTM!


504-524: LGTM!


526-555: LGTM!


4680-4681: LGTM!

Comment thread scripts/codex.js
Comment thread scripts/mcodex
Comment thread scripts/mcodex Outdated
Comment thread scripts/mcodex Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (2)
scripts/mcodex (2)

60-62: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

TOCTOU race in session name generation.

lib/scripts/mcodex:60 checks if session exists, then lib/scripts/mcodex:61 appends timestamp suffix. if two mcodex processes start in the same second, both get identical $suffix from date +%H%M%S, leading to a session name collision when the second process tries to create the session at lib/scripts/mcodex:65. set -e will cause the script to exit with tmux's error, but this is a poor user experience.

consider appending $$ (process id) to the suffix for uniqueness.

🔒 append pid to suffix
   session="${MCODEX_TMUX_SESSION:-mcodex}"
-  suffix="$(date +%H%M%S)"
+  suffix="$(date +%H%M%S)-$$"
   if tmux has-session -t "$session" 2>/dev/null; then
🤖 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 `@scripts/mcodex` around lines 60 - 62, The session name generation has a
TOCTOU race because suffix is just date +%H%M%S and two processes can pick the
same value; change the suffix generation to include the current PID (append $$)
so suffix becomes time+pid and then use that suffix when setting session
(referenced by the variables suffix and session and the tmux check using tmux
has-session); ensure the code still checks tmux has-session -t "$session" and
then sets session="${session}-${suffix}" with the new PID-enhanced suffix to
guarantee uniqueness.

1-76: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

no test coverage for new mcodex bash entrypoint.

lib/scripts/mcodex adds 76 lines of bash logic (monitor mode, tmux session management, live-accounts split, quote_args, configure_tmux_scrollback) with zero test coverage. regression tests should validate:

  • --monitor invokes watch with correct interval
  • --tmux creates session and attaches when tmux available
  • --tmux --live-accounts splits pane
  • session name collision handling (suffix generation)
  • fallback to direct exec when tmux missing or no flags provided
  • configure_tmux_scrollback sets mouse and history-limit

without tests, refactors or env changes can silently break these modes.

do you want me to generate a test scaffold using bats or shunit2, or open a new issue to track test coverage for lib/scripts/mcodex?

🤖 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 `@scripts/mcodex` around lines 1 - 76, Add regression tests covering the new
mcodex bash entrypoint: exercise the script with --monitor to verify run_monitor
invokes watch with $monitor_interval, with --tmux to verify a tmux session is
created/attached and configure_tmux_scrollback sets mouse and history-limit,
with --tmux --live-accounts to verify live_accounts triggers a split-window, a
test for session name collision to validate suffix generation, and fallbacks
when tmux is missing or no flags provided to ensure exec codex-multi-auth-codex
runs; implement these as a test scaffold (bats or shunit2) that stubs/mocks
command -v tmux, tmux subcommands (has-session, new-session, split-window,
set-option, attach-session), watch, and inspects environment variables
monitor_interval, tmux_history_limit, session and suffix to assert correct calls
and options, and add the tests to CI so future refactors of run_monitor,
quote_args, configure_tmux_scrollback, and the top-level flag handling are
covered.
🤖 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 `@scripts/mcodex`:
- Line 53: The tmux commands embed the monitor_interval variable unquoted,
allowing shell injection; update both tmux split-window invocations (the lines
building the watch command that reference $monitor_interval) to pass the
interval as a quoted argument, e.g. invoke tmux split-window with watch -n
"$monitor_interval" so the value is treated as a single safe argument (apply the
same quoting fix to the second occurrence around $monitor_interval).
- Around line 22-31: The tmux calls in configure_tmux_scrollback currently
redirect only stdout (tmux set-option ... >/dev/null) so errors still print;
update the two tmux set-option invocations (inside configure_tmux_scrollback
using target_args and tmux_history_limit) to consistently redirect both stdout
and stderr (e.g., add 2>&1 to the redirection or use >/dev/null 2>&1) or remove
redirection entirely so successful and failing output are treated consistently.

---

Outside diff comments:
In `@scripts/mcodex`:
- Around line 60-62: The session name generation has a TOCTOU race because
suffix is just date +%H%M%S and two processes can pick the same value; change
the suffix generation to include the current PID (append $$) so suffix becomes
time+pid and then use that suffix when setting session (referenced by the
variables suffix and session and the tmux check using tmux has-session); ensure
the code still checks tmux has-session -t "$session" and then sets
session="${session}-${suffix}" with the new PID-enhanced suffix to guarantee
uniqueness.
- Around line 1-76: Add regression tests covering the new mcodex bash
entrypoint: exercise the script with --monitor to verify run_monitor invokes
watch with $monitor_interval, with --tmux to verify a tmux session is
created/attached and configure_tmux_scrollback sets mouse and history-limit,
with --tmux --live-accounts to verify live_accounts triggers a split-window, a
test for session name collision to validate suffix generation, and fallbacks
when tmux is missing or no flags provided to ensure exec codex-multi-auth-codex
runs; implement these as a test scaffold (bats or shunit2) that stubs/mocks
command -v tmux, tmux subcommands (has-session, new-session, split-window,
set-option, attach-session), watch, and inspects environment variables
monitor_interval, tmux_history_limit, session and suffix to assert correct calls
and options, and add the tests to CI so future refactors of run_monitor,
quote_args, configure_tmux_scrollback, and the top-level flag handling are
covered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0f8ed299-86d1-444b-a73e-b7e9c3d2c14c

📥 Commits

Reviewing files that changed from the base of the PR and between de9ce86 and 00a5656.

📒 Files selected for processing (1)
  • scripts/mcodex
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review
🔇 Additional comments (5)
scripts/mcodex (5)

8-16: trailing space in quote_args output may cause empty arg injection.

printf '%s ' "${quoted[@]}" at lib/scripts/mcodex:15 appends a trailing space after the last argument. when interpolated into the tmux command at lib/scripts/mcodex:65, the trailing space becomes part of the command string.


18-20: run_monitor ignores the "$@" passed at lib/scripts/mcodex:35.

lib/scripts/mcodex:35 passes remaining args to run_monitor, but the function doesn't use any parameters.


64-65: tmux command string may mishandle arguments with embedded quotes or special chars.

lib/scripts/mcodex:64-65 pre-quotes $args via quote_args, then embeds the result in double quotes for tmux. this double-quoting can break for arguments containing quotes.


1-6: LGTM!


75-75: LGTM!

Comment thread scripts/mcodex
Comment thread scripts/mcodex
Security:
- scripts/mcodex: validate MCODEX_MONITOR_INTERVAL (and MCODEX_TMUX_HISTORY_LIMIT)
  as numeric before they are interpolated into the `watch -n <n> ...` / tmux
  option strings that tmux hands to a shell. A value like "5; rm -rf ~" or
  "$(cmd)" was previously executed verbatim; it is now rejected and replaced with
  the safe default. Closes the command-injection findings on lines 53/68.

Correctness:
- scripts/codex.js maybeRefreshQuotaCacheInBackground: the status-refresh child's
  close/error handlers called the Atomics.wait-backed removeDirectoryWithRetry,
  blocking the PARENT event loop up to ~200ms on a transient Windows lock while
  Codex was running. Added removeDirectoryBestEffortAsync (fsPromises.rm with
  internal retry, no blocking sleep) and use it in those callbacks.
- scripts/codex.js formatStatusPath: the `~` abbreviation used a hardcoded "/"
  separator, so on Windows (backslash paths from resolvePath) the cwd was never
  abbreviated. Use path.sep for the boundary check and normalize the remainder to
  forward slashes for a stable status line.

mcodex polish:
- quote_args no longer emits a trailing space (avoids an empty trailing token in
  the interpolated tmux command).
- the tmux new-session command is built in a variable from printf %q-escaped args
  so arguments with embedded quotes/spaces are passed safely.
- --monitor no longer forwards unused "$@"; configure_tmux_scrollback redirects
  stderr too for consistent silence.

Docs / known-race notes:
- documented the bounded TOCTOU in the stale-lock recovery (a slow-but-alive
  refresh owner can be evicted, briefly yielding two idempotent refresh children)
  and the detached-child caveat (close/error cleanup may not fire if the parent
  exits first; 10-minute stale-lock recovery reclaims the lock).

Tests:
- test/documentation.test.ts: add `mcodex` to the canonical bin surface the PR
  introduced (the assertion was left stale by the PR and was failing).
- test/mcodex-launcher.test.ts (new): regression for the interval-injection
  guard — valid integer/fractional pass through; injection payloads and shell
  substitutions collapse to the safe default; asserts the shipped script still
  contains the numeric guard. Skips where bash is unavailable.

typecheck + lint clean; full suite 4067 passed.
@ndycode
Copy link
Copy Markdown
Owner

ndycode commented Jun 2, 2026

Review fixes — mcodex launcher + statusline

Pushed c2ded64. Addressed all 10 threads. Also fixed a pre-existing test break the PR introduced.

Security

Finding Fix
scripts/mcodex:53,68$monitor_interval command injection (P1 / Major) MCODEX_MONITOR_INTERVAL (and MCODEX_TMUX_HISTORY_LIMIT) are now validated as numeric before interpolation into the watch -n … / tmux-option strings. 5; rm -rf ~, $(cmd), backticks → rejected, replaced with the safe default.

Correctness

Finding Fix
scripts/codex.js:591 — blocking sleepSync in child close/error callbacks (P1) Those handlers fire on the parent event loop while Codex runs; the Atomics.wait backoff could freeze it ~200ms on a Windows lock. Added removeDirectoryBestEffortAsync (fsPromises.rm w/ internal retry, no blocking sleep) for the callback path.
scripts/codex.js:395formatStatusPath hardcoded /, breaks ~ on Windows (P2) Uses path.sep for the boundary check; remainder normalized to forward slashes for a stable status line.

mcodex polish

Known-race docs (#3, #5)

  • Documented the bounded stale-lock TOCTOU (a slow-but-alive refresh owner can be evicted → two idempotent refresh children) and the detached-child caveat (cleanup may not fire if the parent exits first; 10-min stale-lock recovery reclaims it).

Tests

  • test/documentation.test.ts: the PR added the mcodex bin but left the canonical-bin-surface assertion stale — it was failing on the PR head. Added mcodex to the expected map.
  • test/mcodex-launcher.test.ts (new): regression for the interval-injection guard — valid integer/fractional pass through; injection payloads + shell substitutions collapse to the default; asserts the shipped script still carries the numeric guard. Skips where bash is unavailable.

Verification: typecheck clean, lint clean, full suite 4067 passed.

@ndycode
Copy link
Copy Markdown
Owner

ndycode commented Jun 2, 2026

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 2, 2026

✅ Actions performed

Full review triggered.

Comment thread scripts/codex.js Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (2)
test/documentation.test.ts (1)

538-586: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

also assert that scripts/mcodex is published.

test/documentation.test.ts:581-586 locks the new bin entry, but the packaging contract also depends on package.json files including scripts/mcodex. without that assertion, npm can publish a broken mcodex shim while this test still passes.

proposed fix
 		const packageJson = JSON.parse(read("package.json")) as {
 			name?: unknown;
 			description?: unknown;
 			keywords?: unknown;
 			author?: unknown;
 			license?: unknown;
 			repository?: { url?: unknown } | unknown;
 			homepage?: unknown;
 			bugs?: { url?: unknown } | unknown;
 			bin?: Record<string, unknown> | unknown;
+			files?: unknown;
 		};
@@
 		expect(packageJson.bin).toEqual({
 			mcodex: "scripts/mcodex",
 			"codex-multi-auth-app-launcher": "scripts/codex-app-launcher.js",
 			"codex-multi-auth-codex": "scripts/codex.js",
 			"codex-multi-auth": "scripts/codex-multi-auth.js",
 		});
+		expect(packageJson.files).toEqual(
+			expect.arrayContaining(["scripts/mcodex"]),
+		);
🤖 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 `@test/documentation.test.ts` around lines 538 - 586, The test currently
asserts packageJson.bin entries but doesn't ensure the uploaded package actually
includes the mcodex script; update the "keeps package metadata aligned with the
canonical owner surface" test to also assert that packageJson.files (or
packageJson.files as an array) contains "scripts/mcodex" — locate the test using
the packageJson variable and add an expectation like
expect(packageJson.files).toEqual(expect.arrayContaining(["scripts/mcodex"]));
to guarantee the mcodex shim is published.
scripts/mcodex (1)

38-40: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

gate monitor modes on watch availability.

scripts/mcodex:38-40, scripts/mcodex:78, and scripts/mcodex:101 invoke watch unconditionally. --monitor and --live-accounts should fail fast with a clear error when watch isn’t installed (there’s a tmux check, but no equivalent watch guard: scripts/mcodex:70-73).

missing regression tests: test/mcodex-launcher.test.ts:6-12 covers interval injection hardening, not the missing-watch dependency failure path (test/mcodex-launcher.test.ts:41-67).

windows edge cases: tests skip when bash is unavailable (test/mcodex-launcher.test.ts:12,17-20), but git-bash/msys users still need this runtime guard.

concurrency risk: every live pane spawns its own watch process; failing fast prevents leaving broken panes/processes around during concurrent launches.

proposed fix
+ensure_watch() {
+  if ! command -v watch >/dev/null 2>&1; then
+    echo "mcodex: watch is required for --monitor and --live-accounts" >&2
+    exit 1
+  fi
+}
+
 run_monitor() {
+  ensure_watch
   watch -n "$monitor_interval" 'codex-multi-auth list'
 }
@@
   if [[ "${1:-}" == "--live-accounts" ]]; then
     live_accounts=1
     shift
+    ensure_watch
   fi
🤖 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 `@scripts/mcodex` around lines 38 - 40, The script calls watch unconditionally
in run_monitor() (and at other spots for --monitor and --live-accounts) so add a
runtime guard that checks for watch availability (e.g., command -v watch
>/dev/null) before spawning it; implement a small helper (check_watch_available
or ensure_watch) and call it from run_monitor and wherever
--monitor/--live-accounts create panes so the script exits with a clear error
message if watch is missing; update the launcher paths that currently only check
tmux (the same guard used around tmux checks at the earlier block) and
add/adjust tests to cover the missing-watch failure path in
test/mcodex-launcher.test.ts so the CLI fails fast when watch is not installed.
🤖 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 `@scripts/mcodex`:
- Around line 49-55: The tmux wheel-bindings in configure_tmux_scrollback() (the
tmux bind-key -T ... lines) are applied server‑globally and not scoped or
reverted; change the implementation to either 1) apply bindings to an isolated
tmux server/socket for mcodex runs (use tmux -L / -S to create/use a per-run
socket) or 2) scope and restore bindings per-session by saving existing
bindings, using session-targeted commands and restoring them on exit (use the
same session identifier used for tmux set-option and add matching tmux
unbind-key calls to revert), and add a regression test that asserts bindings are
reverted or that an isolated tmux server/socket is used; update the tmux
bind-key lines in scripts/mcodex (the WheelUpPane/WheelDownPane bind-key
entries) and add matching unbind/restore logic and tests in
test/mcodex-launcher.test.ts.

In `@test/mcodex-launcher.test.ts`:
- Around line 22-38: The test currently hardcodes the launcher prologue in
resolveInterval and text-matches the file later; instead read the real
scripts/mcodex file, extract the actual prologue block (from the top until the
first tmux/exec/exec line or another clear delimiter used in scripts/mcodex),
and run that extracted prologue with spawnSync (preserving the same env
injection pattern used now) so resolveInterval executes the shipped regex rather
than a copy; also update the assertions around the later text-match (the block
referenced at lines 61-66) to compare against the extracted prologue from
scripts/mcodex rather than an in-test literal. Ensure you update references to
resolveInterval and the test assertions to use the file-read+extract logic.
- Line 4: Remove the named import of describe, expect, and it from vitest in the
test file and use the vitest globals instead: delete the line importing {
describe, expect, it } from "vitest" so the test calls to describe, expect, and
it use the configured globals (references: describe, expect, it, vitest).

---

Outside diff comments:
In `@scripts/mcodex`:
- Around line 38-40: The script calls watch unconditionally in run_monitor()
(and at other spots for --monitor and --live-accounts) so add a runtime guard
that checks for watch availability (e.g., command -v watch >/dev/null) before
spawning it; implement a small helper (check_watch_available or ensure_watch)
and call it from run_monitor and wherever --monitor/--live-accounts create panes
so the script exits with a clear error message if watch is missing; update the
launcher paths that currently only check tmux (the same guard used around tmux
checks at the earlier block) and add/adjust tests to cover the missing-watch
failure path in test/mcodex-launcher.test.ts so the CLI fails fast when watch is
not installed.

In `@test/documentation.test.ts`:
- Around line 538-586: The test currently asserts packageJson.bin entries but
doesn't ensure the uploaded package actually includes the mcodex script; update
the "keeps package metadata aligned with the canonical owner surface" test to
also assert that packageJson.files (or packageJson.files as an array) contains
"scripts/mcodex" — locate the test using the packageJson variable and add an
expectation like
expect(packageJson.files).toEqual(expect.arrayContaining(["scripts/mcodex"]));
to guarantee the mcodex shim is published.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2de4a735-bd55-4b14-86a3-82b7ca068594

📥 Commits

Reviewing files that changed from the base of the PR and between 00a5656 and c2ded64.

📒 Files selected for processing (4)
  • scripts/codex.js
  • scripts/mcodex
  • test/documentation.test.ts
  • test/mcodex-launcher.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (AGENTS.md)

Use ESM only ("type": "module"), Node >= 18

Files:

  • test/documentation.test.ts
  • test/mcodex-launcher.test.ts
  • scripts/codex.js
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use as any, @ts-ignore, or @ts-expect-error TypeScript assertions

Files:

  • test/documentation.test.ts
  • test/mcodex-launcher.test.ts
{**/scripts/**/*.js,**/test/**/*.ts}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use bare recursive delete logic in Windows-sensitive scripts/tests without retry handling for transient EBUSY/EPERM/ENOTEMPTY errors

Files:

  • test/documentation.test.ts
  • test/mcodex-launcher.test.ts
  • scripts/codex.js
test/**/*.test.ts

📄 CodeRabbit inference engine (test/AGENTS.md)

test/**/*.test.ts: Vitest globals (describe, it, expect) are enabled and should be used without explicit imports
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/ENOTEMPTY backoff
Use source files in tests, not compiled dist/ files; test the source directly
Do not skip tests without justification; include rationale if a test must be skipped
Relax ESLint rules for test files as specified in eslint.config.js

Files:

  • test/documentation.test.ts
  • test/mcodex-launcher.test.ts
test/**/documentation.test.ts

📄 CodeRabbit inference engine (test/AGENTS.md)

Test documentation parity including command flags, config precedence, changelog policy, and governance rules

Files:

  • test/documentation.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/documentation.test.ts
  • test/mcodex-launcher.test.ts
**/scripts/codex*.js

📄 CodeRabbit inference engine (AGENTS.md)

Do not bypass the official Codex CLI by reimplementing general Codex commands in the wrapper

Files:

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

📄 CodeRabbit inference engine (AGENTS.md)

The package does not publish a global codex bin; codex-multi-auth-codex is the explicit wrapper

Files:

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

21-25: LGTM!

Also applies to: 170-190, 395-404, 571-579, 602-625

Comment thread scripts/mcodex
Comment thread test/mcodex-launcher.test.ts
Comment thread test/mcodex-launcher.test.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (3)
test/mcodex-launcher.test.ts (3)

22-38: 🧹 Nitpick | 🔵 Trivial | ⚖️ Poor tradeoff

test harness duplicates validation logic instead of extracting from shipped script.

test/mcodex-launcher.test.ts:26-33 hardcodes the interval-validation block rather than extracting it from scripts/mcodex. if the script's regex or fallback logic changes, this test will pass against stale logic. the comment at lines 22-24 claims extraction but the implementation copies.

consider reading scripts/mcodex and extracting the prologue up to quote_args() so the test exercises the actual shipped code.

🤖 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 `@test/mcodex-launcher.test.ts` around lines 22 - 38, The test duplicates the
interval-validation prologue instead of using the actual shipped script; update
resolveInterval to read scripts/mcodex, extract the prologue up to the unique
marker/function name quote_args() (or the first occurrence of that token), and
then run that extracted shell snippet (via spawnSync/bash) with
MCODEX_MONITOR_INTERVAL set to the test value so the test exercises the real
regex/fallback logic from scripts/mcodex rather than a hardcoded copy.

4-4: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

remove the explicit vitest import; globals are enabled for test/**/*.test.ts.

test/mcodex-launcher.test.ts:4 imports { describe, expect, it } from "vitest" but vitest.config.ts sets globals: true for test files. drop the import and use the globals directly.

-import { describe, expect, it } from "vitest";

as per coding guidelines: "Vitest globals (describe, it, expect) are enabled and should be used without explicit imports"

🤖 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 `@test/mcodex-launcher.test.ts` at line 4, Remove the explicit import of Vitest
symbols and rely on enabled globals: delete the import statement that imports {
describe, expect, it } from "vitest" and use the global describe, it, and expect
identifiers directly in the test file (ensure no other references to the named
import remain). This keeps the test consistent with the config's globals: true
setting and coding guidelines.

61-66: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

use readFileSync instead of spawning bash to cat the file.

test/mcodex-launcher.test.ts:63-65 spawns bash just to read the script file. readFileSync(mcodexPath, "utf-8") is simpler, faster, and doesn't require bash.

+import { readFileSync } from "node:fs";
...
 it("the shipped script actually contains the numeric guard", () => {
-  const res = spawnSync("bash", ["-c", `cat "${mcodexPath}"`], {
-    encoding: "utf-8",
-  });
-  expect(res.stdout).toMatch(/\^\[0-9\]\+\(\\\.\[0-9\]\+\)\?\$/);
+  expect(readFileSync(mcodexPath, "utf-8")).toMatch(
+    /\^\[0-9\]\+\(\\\.\[0-9\]\+\)\?\$/,
+  );
 });
🤖 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 `@test/mcodex-launcher.test.ts` around lines 61 - 66, The test currently shells
out with spawnSync to cat the file; replace that with a direct file read using
fs.readFileSync to avoid spawning bash: import or require readFileSync from
"fs", read the file into a string using readFileSync(mcodexPath, "utf-8")
(replace the spawnSync call and the res variable), and keep the same assertion
against that string (the expect(...).toMatch(...) should use the read content).
Ensure mcodexPath remains the same identifier and remove the spawnSync
import/use.
🤖 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.

Duplicate comments:
In `@test/mcodex-launcher.test.ts`:
- Around line 22-38: The test duplicates the interval-validation prologue
instead of using the actual shipped script; update resolveInterval to read
scripts/mcodex, extract the prologue up to the unique marker/function name
quote_args() (or the first occurrence of that token), and then run that
extracted shell snippet (via spawnSync/bash) with MCODEX_MONITOR_INTERVAL set to
the test value so the test exercises the real regex/fallback logic from
scripts/mcodex rather than a hardcoded copy.
- Line 4: Remove the explicit import of Vitest symbols and rely on enabled
globals: delete the import statement that imports { describe, expect, it } from
"vitest" and use the global describe, it, and expect identifiers directly in the
test file (ensure no other references to the named import remain). This keeps
the test consistent with the config's globals: true setting and coding
guidelines.
- Around line 61-66: The test currently shells out with spawnSync to cat the
file; replace that with a direct file read using fs.readFileSync to avoid
spawning bash: import or require readFileSync from "fs", read the file into a
string using readFileSync(mcodexPath, "utf-8") (replace the spawnSync call and
the res variable), and keep the same assertion against that string (the
expect(...).toMatch(...) should use the read content). Ensure mcodexPath remains
the same identifier and remove the spawnSync import/use.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a3ca1021-0d60-437a-96ea-2ff34ffdf20d

📥 Commits

Reviewing files that changed from the base of the PR and between 70ab38a and c2ded64.

📒 Files selected for processing (5)
  • package.json
  • scripts/codex.js
  • scripts/mcodex
  • test/documentation.test.ts
  • test/mcodex-launcher.test.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (AGENTS.md)

Use ESM only ("type": "module"), Node >= 18

Files:

  • test/documentation.test.ts
  • test/mcodex-launcher.test.ts
  • scripts/codex.js
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use as any, @ts-ignore, or @ts-expect-error TypeScript assertions

Files:

  • test/documentation.test.ts
  • test/mcodex-launcher.test.ts
{**/scripts/**/*.js,**/test/**/*.ts}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use bare recursive delete logic in Windows-sensitive scripts/tests without retry handling for transient EBUSY/EPERM/ENOTEMPTY errors

Files:

  • test/documentation.test.ts
  • test/mcodex-launcher.test.ts
  • scripts/codex.js
test/**/*.test.ts

📄 CodeRabbit inference engine (test/AGENTS.md)

test/**/*.test.ts: Vitest globals (describe, it, expect) are enabled and should be used without explicit imports
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/ENOTEMPTY backoff
Use source files in tests, not compiled dist/ files; test the source directly
Do not skip tests without justification; include rationale if a test must be skipped
Relax ESLint rules for test files as specified in eslint.config.js

Files:

  • test/documentation.test.ts
  • test/mcodex-launcher.test.ts
test/**/documentation.test.ts

📄 CodeRabbit inference engine (test/AGENTS.md)

Test documentation parity including command flags, config precedence, changelog policy, and governance rules

Files:

  • test/documentation.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/documentation.test.ts
  • test/mcodex-launcher.test.ts
package.json

📄 CodeRabbit inference engine (SECURITY.md)

package.json: Pin hono to version 4.12.14 or higher to avoid authentication bypass vulnerability in GHSA-xh87-mx6m-69f3
Pin rollup to version ^4.59.0 or higher to avoid vulnerable versions <4.59.0 in Vite and Vitest transitive dependencies

Files:

  • package.json
**/scripts/codex*.js

📄 CodeRabbit inference engine (AGENTS.md)

Do not bypass the official Codex CLI by reimplementing general Codex commands in the wrapper

Files:

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

📄 CodeRabbit inference engine (AGENTS.md)

The package does not publish a global codex bin; codex-multi-auth-codex is the explicit wrapper

Files:

  • scripts/codex.js
🔇 Additional comments (15)
scripts/codex.js (11)

595-628: lock cleanup caveat is now documented inline.

the past review comment about lock cleanup handlers not firing if parent exits immediately is addressed by the comment at lines 602-605. the 10-minute stale-lock recovery in acquireStatusRefreshLock handles orphaned locks. this is acceptable for the use case.


21-21: LGTM!

Also applies to: 24-24, 85-87


170-190: LGTM!


205-258: LGTM!


260-316: LGTM!


318-389: LGTM!


391-406: LGTM!


408-524: LGTM!


526-554: LGTM!


556-593: LGTM!


4716-4717: LGTM!

scripts/mcodex (1)

1-108: LGTM!

package.json (1)

117-117: LGTM!

Also applies to: 132-132

test/documentation.test.ts (1)

582-582: LGTM!

test/mcodex-launcher.test.ts (1)

41-59: LGTM!

P1 (Greptile): the forwarded status line resolved accounts via the global
`resolveAccountsPath` (~/.codex/multi-auth/openai-codex-accounts.json), so inside
a project with a per-project pool it either showed no status line (global pool
empty) or showed the wrong account/quota — silently contradicting the account
Codex actually routes through.

Fix: add `resolveStatusAccountsDir`, which mirrors the runtime's own account
scoping (lib/runtime/account-scope.ts) by reusing the built dist helpers (never
re-deriving project keys from raw paths, per AGENTS.md):
  - perProjectAccounts enabled + Codex CLI sync OFF + cwd resolves to a project
    root → per-project pool via getProjectGlobalConfigDir(
    resolveProjectStorageIdentityRoot(projectRoot));
  - otherwise (no project, sync on, config off, or dist unavailable) → global.
Only the ACCOUNTS pool is per-project; quota-cache.json and
runtime-observability.json stay global (lib: getCodexMultiAuthDir), so those
reads are unchanged. `maybePrintForwardStatusLine` is now async to resolve the
dir before formatting; any failure falls back to the global dir so the launcher
never breaks.

Note: the per-project pool is grounded in the dist config dir, which itself
honors CODEX_MULTI_AUTH_DIR / CODEX_HOME — so no special-casing of those envs
here, or the status line would diverge from where the runtime writes.

test/mcodex-statusline-scope.test.ts: end-to-end regressions over the real
wrapper + dist build — per-project pool is shown inside a project, global pool
outside one, and global when Codex CLI sync is enabled (matching account-scope).

typecheck + lint clean; full suite 4070 passed.
@ndycode
Copy link
Copy Markdown
Owner

ndycode commented Jun 2, 2026

Follow-up — per-project account pool (P1)

Pushed 7e03e1d. Fixes the remaining P1.

scripts/codex.js — status line read the GLOBAL accounts pool, dropping per-project pools.

resolveAccountsPath always resolved ~/.codex/multi-auth/openai-codex-accounts.json, so inside a project with a per-project pool the status line either didn't appear (global empty) or showed the wrong account/quota — contradicting what Codex actually routes through.

Fix: added resolveStatusAccountsDir, which mirrors the runtime's own scoping (lib/runtime/account-scope.ts) by reusing the built dist helpers (no re-derivation of project keys from raw paths, per AGENTS.md):

  • perProjectAccounts on + Codex CLI sync off + cwd in a project root → per-project pool via getProjectGlobalConfigDir(resolveProjectStorageIdentityRoot(root));
  • otherwise → global.

Only the accounts pool is per-project — quota-cache.json and runtime-observability.json are global (getCodexMultiAuthDir), so those reads are unchanged. maybePrintForwardStatusLine is now async; any failure falls back to the global dir so the launcher never breaks. The per-project pool is grounded in the dist config dir, which honors CODEX_MULTI_AUTH_DIR / CODEX_HOME, so there's no env special-casing that would diverge from where the runtime writes.

test/mcodex-statusline-scope.test.ts (new): end-to-end over the real wrapper + dist build — project pool shown inside a project, global outside one, and global when Codex CLI sync is enabled (matching account-scope).

Verification: typecheck clean, lint clean, full suite 4070 passed.

@ndycode
Copy link
Copy Markdown
Owner

ndycode commented Jun 2, 2026

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 2, 2026

✅ Actions performed

Full review triggered.

Comment thread scripts/codex.js
Comment thread test/mcodex-statusline-scope.test.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 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 `@scripts/codex.js`:
- Around line 232-280: The resolveStatusAccountsDir function currently swallows
import/resolution failures and early-returns without any visibility; update
resolveStatusAccountsDir to accept an optional cwd parameter (defaulting to
process.cwd()) and add debug logging (when env.CODEX_MULTI_AUTH_DEBUG is truthy)
at each failure point: when dynamic imports fail, when required functions are
missing (check loadPluginConfig, getPerProjectAccounts, findProjectRoot,
resolveProjectStorageIdentityRoot, getProjectGlobalConfigDir), when
getPerProjectAccounts !== true, and when isCodexCliSyncEnabled() forces
globalDir; use resolveMultiAuthDirFromEnv as the fallback value and include the
caught error details in the debug log inside the catch so developers can
diagnose missing dist helpers or wrong project roots.

In `@test/mcodex-statusline-scope.test.ts`:
- Around line 71-72: The test uses a non-existent flag "--version-noop" in
runWrapper which could collide with future real flags; update the runWrapper
invocation in test/mcodex-statusline-scope.test.ts (the runWrapper function) to
use a clearly fake arg such as "--noop-for-test" or remove the flag entirely to
exercise the TUI path, and ensure any downstream expectations still hold (i.e.,
keep spawnSync(process.execPath, [wrapperScript, ...]) but replace
"--version-noop" with the chosen safe token or omit it).
- Line 9: Remove the unused import createHash from the top of
test/mcodex-statusline-scope.test.ts: locate the import statement "import {
createHash } from 'node:crypto';" and delete it (or remove createHash from the
import list) so there are no dead imports referenced by this test file.
- Line 12: Remove the explicit Vitest imports and rely on the enabled globals:
delete the import line that brings in afterEach, describe, expect, it and use
the global identifiers directly in the test file (references: afterEach,
describe, it, expect) so the test adheres to the guideline of using Vitest
globals without importing.
- Around line 28-41: The afterEach cleanup loop uses rmSync and a tight retry
loop which lacks backoff; replace it with the recommended removeWithRetry helper
(or add a small synchronous backoff between attempts) to handle transient
EBUSY/EPERM/ENOTEMPTY on Windows: locate the afterEach block referencing
createdDirs and rmSync, import or call removeWithRetry (or, if helper not
available, implement a short sync sleep via Atomics.wait between attempts) and
ensure the cleanup uses that function instead of the bare rmSync retry loop so
tests reliably cleanup on Windows.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2aee1f99-830d-47a9-b959-1874aaafcd10

📥 Commits

Reviewing files that changed from the base of the PR and between c2ded64 and 7e03e1d.

📒 Files selected for processing (2)
  • scripts/codex.js
  • test/mcodex-statusline-scope.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (AGENTS.md)

Use ESM only ("type": "module"), Node >= 18

Files:

  • test/mcodex-statusline-scope.test.ts
  • scripts/codex.js
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use as any, @ts-ignore, or @ts-expect-error TypeScript assertions

Files:

  • test/mcodex-statusline-scope.test.ts
{**/scripts/**/*.js,**/test/**/*.ts}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use bare recursive delete logic in Windows-sensitive scripts/tests without retry handling for transient EBUSY/EPERM/ENOTEMPTY errors

Files:

  • test/mcodex-statusline-scope.test.ts
  • scripts/codex.js
test/**/*.test.ts

📄 CodeRabbit inference engine (test/AGENTS.md)

test/**/*.test.ts: Vitest globals (describe, it, expect) are enabled and should be used without explicit imports
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/ENOTEMPTY backoff
Use source files in tests, not compiled dist/ files; test the source directly
Do not skip tests without justification; include rationale if a test must be skipped
Relax ESLint rules for test files as specified in eslint.config.js

Files:

  • test/mcodex-statusline-scope.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/mcodex-statusline-scope.test.ts
**/scripts/codex*.js

📄 CodeRabbit inference engine (AGENTS.md)

Do not bypass the official Codex CLI by reimplementing general Codex commands in the wrapper

Files:

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

📄 CodeRabbit inference engine (AGENTS.md)

The package does not publish a global codex bin; codex-multi-auth-codex is the explicit wrapper

Files:

  • scripts/codex.js
🔇 Additional comments (8)
scripts/codex.js (5)

649-682: lock cleanup is now non-blocking—acknowledged caveat about detached child is documented.

the inline comment at lines 656-659 documents the detached-child stale-lock TOCTOU risk, matching the past review comment. the 10-minute stale-lock recovery is the mitigation. this is acceptable given the idempotent, read-mostly nature of forecast refresh.

one edge case: if many parallel mcodex invocations hit a slow forecast child (>10m), the stale-lock eviction could spawn duplicate children briefly. the risk is benign (redundant cache writes) but worth noting if you ever add heavier work to this path.


220-231: LGTM!


537-578: LGTM!


179-190: LGTM!


4770-4771: LGTM!

test/mcodex-statusline-scope.test.ts (3)

94-133: LGTM!


135-154: LGTM!


156-190: LGTM!

Comment thread scripts/codex.js
Comment thread test/mcodex-statusline-scope.test.ts
Comment thread test/mcodex-statusline-scope.test.ts
Comment thread test/mcodex-statusline-scope.test.ts
Comment thread test/mcodex-statusline-scope.test.ts
Follow-up review findings:

- scripts/mcodex (Major): `watch` was invoked unconditionally in run_monitor and
  the two tmux live-account panes, with no equivalent of the existing `tmux`
  guard. Added require_watch; --monitor now fails fast with an actionable error
  and propagates a non-zero exit (exit $? instead of exit 0), and each live pane
  is gated on `&& require_watch` so a missing `watch` skips the pane instead of
  spawning a broken one (Codex still launches).

- test/documentation.test.ts (Minor): the bin assertion locked the new `mcodex`
  entry but nothing verified package.json files[] publishes the shim, so npm
  could ship a bin pointing at a missing script while the test passed. Now every
  declared bin target is asserted present in files[].

Tests: test/mcodex-launcher.test.ts gains a missing-`watch` runtime guard case
(drives require_watch with `watch` shadowed absent → non-zero + clear error) and
a static check that all three watch sites are wired through require_watch.

typecheck + lint clean; full suite 4072 passed.
@ndycode
Copy link
Copy Markdown
Owner

ndycode commented Jun 2, 2026

Follow-up 2 — watch guard + bin-publish assertion

Pushed 1a57939.

  • scripts/mcodex (Major): gate monitor modes on watch. run_monitor and both tmux live-account panes invoked watch unconditionally — no equivalent of the existing tmux guard. Added require_watch: --monitor now fails fast with an actionable error and propagates a non-zero exit (exit $?, was exit 0); each live pane is gated && require_watch, so a missing watch skips the pane rather than spawning a broken one (Codex still launches).
  • test/documentation.test.ts (Minor): assert bins are published. The bin map was locked but nothing verified files[] ships each shim, so npm could publish a bin pointing at a missing script while the test passed. Now every declared bin target is asserted present in files[].

Tests (test/mcodex-launcher.test.ts): a missing-watch runtime case (drives require_watch with watch shadowed absent → non-zero + clear error) and a static check that all three watch sites route through require_watch.

Verification: typecheck clean, lint clean, full suite 4072 passed.

@ndycode
Copy link
Copy Markdown
Owner

ndycode commented Jun 2, 2026

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 2, 2026

✅ Actions performed

Full review triggered.

@ndycode ndycode merged commit d0addd9 into ndycode:main Jun 2, 2026
2 checks passed
ndycode added a commit that referenced this pull request Jun 2, 2026
- Bump vitest / @vitest/ui / @vitest/coverage-v8 to ^4.1.8 (dev-only), clearing
  GHSA-5xrq-8626-4rwp so `npm run audit:ci` exits 0. audit:prod was already
  clean (nothing test-related ships); this unblocks the CI release gate.
- Version → 2.1.13-beta.3.
- Add docs/releases/v2.1.13-beta.3.md covering the Phase 1 audit (#499), the
  mcodex launcher + statusline (#500), and the pre-release hardening (#503).
  Update the docs portal + root README prerelease pointers (documentation.test.ts
  parity).

typecheck + lint + audit:ci clean; full suite 4246 passed / 1 skipped on vitest
4.1.8; documentation parity 25/25.
ndycode added a commit that referenced this pull request Jun 2, 2026
…#503)

* fix(security,resilience): pre-release hardening from deep stress test

Found by a whole-tree adversarial audit + runtime fuzz of the merged main
(both #499 and #500). The tree was already well-hardened — these are the
residual MEDIUM/LOW gaps, all verified.

Egress hardening (never forward inbound client credentials upstream):
- runtime-rotation-proxy.ts + local-bridge.ts: strip inbound `cookie` and
  `proxy-authorization` on the outbound request, alongside the existing
  authorization/x-api-key stripping. A client cookie would otherwise ride
  upstream with the managed OAuth Bearer.
- runtime-rotation-proxy.ts readErrorBody: the outbound fetch's abort timer is
  cleared once headers arrive, so a stalled ERROR body (429/5xx path) could hang
  the handler forever — the success path is per-chunk stall-bounded, the error
  path was not. Read it via a reader with a per-read idle timeout + a 1MB cap,
  cancelling the stream on stall/overflow.

Windows fs resilience (close the transient-lock gaps the fuzz reproduced):
- storage.ts: the secret-bearing WAL write and temp write had NO retry, and the
  temp cleanup was single-shot — a transient EBUSY/EPERM could fail a valid save
  or strand a token-bearing *.tmp. Wrap all three in withFileOperationRetry. Also
  align copyFileWithRetry / renameFileWithRetry to the shared
  shouldRetryFileOperation taxonomy (adds ENOTEMPTY/EACCES/EAGAIN). The primary
  commit rename keeps its existing EPERM/EBUSY contract unchanged.
- quota-cache.ts: widen RETRYABLE_FS_CODES to the shared set.

Least-privilege:
- runtime/runtime-observability.ts: persist the snapshot (account id/label) with
  mode 0o600 and the dir 0o700, matching the other sensitive writers (no-op on
  win32, owner-only on POSIX).

Tests: cookie strip on both egress paths; existing storage WAL/temp/backup retry
coverage still green (rename contract unchanged). Also cleaned a stale
eslint-disable + unused import in the merged mcodex statusline test.

typecheck + lint clean; full suite 4246 passed / 1 skipped.

* chore(release): cut v2.1.13-beta.3 + bump vitest to ^4.1.8

- Bump vitest / @vitest/ui / @vitest/coverage-v8 to ^4.1.8 (dev-only), clearing
  GHSA-5xrq-8626-4rwp so `npm run audit:ci` exits 0. audit:prod was already
  clean (nothing test-related ships); this unblocks the CI release gate.
- Version → 2.1.13-beta.3.
- Add docs/releases/v2.1.13-beta.3.md covering the Phase 1 audit (#499), the
  mcodex launcher + statusline (#500), and the pre-release hardening (#503).
  Update the docs portal + root README prerelease pointers (documentation.test.ts
  parity).

typecheck + lint + audit:ci clean; full suite 4246 passed / 1 skipped on vitest
4.1.8; documentation parity 25/25.

* fix: address #503 review (dir-mode + retry/strip coverage + version pins)

- runtime/runtime-observability.ts: re-assert dir 0o700 via fs.chmod after
  mkdir — mkdir's mode is a no-op on a pre-existing multi-auth dir, so the
  upgrade path kept old (possibly permissive) perms. POSIX-only, best-effort.
- Bump .codex-plugin/plugin.json + AGENTS.md to 2.1.13-beta.3 (plugin-manifest
  test asserts manifest version == package version; the beta.3 bump left them
  stale).

Tests:
- local-bridge + runtime-rotation-proxy: assert inbound `proxy-authorization`
  (alongside cookie) is stripped on the outbound request, so a refactor dropping
  the delete is caught.
- quota-cache: read-retry (EACCES) + rename-retry (ENOTEMPTY) + last-write-wins
  queue-ordering regressions, pinning the widened retry taxonomy.
- runtime-observability-dir-mode (new, POSIX-only): pre-create CODEX_MULTI_AUTH_DIR
  world-writable, persist a snapshot, assert the dir becomes owner-only — proves
  the chmod-after-mkdir hardening on the upgrade path.
- runtime-observability test fs-mock now exposes chmod.

typecheck + lint + audit:ci clean; full suite 4249 passed / 2 skipped.

* fix: address #503 round-2 review (chmod error handling + win32/strip coverage)

- runtime/runtime-observability.ts: stop swallowing every chmod failure. Only
  ENOENT (dir removed by a concurrent process) is ignored; any other failure is
  surfaced rather than silently leaving a world-readable dir holding account
  ids/labels.
- runtime-observability.test.ts: add a win32 case asserting the snapshot persists
  WITHOUT calling chmod (POSIX-only branch), and reset the chmod mock per test.
- local-bridge.test.ts: extend the auth-enabled runtime-proxy path test to assert
  cookie + proxy-authorization are stripped too (parity with the no-key path).

typecheck + lint clean; full suite 4250 passed / 2 skipped.

* test(observability): assert snapshot temp file is written 0o600

Addresses the #503 round-3 review: the win32 persistence test now also asserts
writeFile received mode 0o600, so an owner-only-perms regression on the snapshot
file fails the suite.
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.

2 participants