Skip to content

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

Merged
ndycode merged 5 commits into
mainfrom
fix/fs-retry-taxonomy-and-egress-hardening
Jun 2, 2026
Merged

fix(security,resilience): pre-release hardening from deep stress test#503
ndycode merged 5 commits into
mainfrom
fix/fs-retry-taxonomy-and-egress-hardening

Conversation

@ndycode
Copy link
Copy Markdown
Owner

@ndycode ndycode commented Jun 2, 2026

Summary

Pre-release hardening surfaced by a deep, whole-tree stress test of the merged main (#499 audit + #500 mcodex) — multi-agent adversarial code audit + runtime fuzz. The tree is already well-hardened; these close the residual MEDIUM/LOW gaps. No CRITICAL/HIGH product bugs were found.

Changes

Egress (never forward inbound client credentials upstream)

  • runtime-rotation-proxy.ts + local-bridge.ts: strip inbound cookie + proxy-authorization on the outbound request (joined the existing authorization/x-api-key stripping). A client cookie would otherwise travel upstream with the managed OAuth Bearer.
  • runtime-rotation-proxy.ts readErrorBody: bounded the error-body read (per-read idle timeout + 1 MB cap, cancel on stall). The outbound abort timer is cleared once headers arrive, so a stalled 429/5xx body could previously hang the handler forever — the success path was stall-bounded, the error path wasn't.

Windows fs resilience (gaps the runtime fuzz reproduced)

  • storage.ts: the secret-bearing WAL write and temp write had no retry and temp cleanup was single-shot — a transient EBUSY/EPERM could fail a valid save or strand a token-bearing .tmp. Wrapped all three in withFileOperationRetry. Aligned copyFileWithRetry/renameFileWithRetry to the shared shouldRetryFileOperation taxonomy (+ENOTEMPTY/EACCES/EAGAIN). The primary commit rename keeps its existing EPERM/EBUSY 5-attempt contract unchanged.
  • quota-cache.ts: widened RETRYABLE_FS_CODES to the shared set.

Least-privilege

  • runtime/runtime-observability.ts: snapshot now written 0o600 / dir 0o700 (account id/label), matching the other sensitive writers. No-op on win32, owner-only on POSIX.

Tests

Cookie-strip regressions on both egress paths; existing storage WAL/temp/backup retry coverage stays green (rename contract intact). Cleaned a stale eslint-disable + unused import left in the merged mcodex statusline test.

Verification

typecheck clean · lint clean · full suite 4246 passed / 1 skipped.

Out of scope (flagged for follow-up, not in this PR)

🤖 Generated with Claude Code

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

pre-release hardening pass closing medium/low gaps surfaced by adversarial stress testing on main. no critical product bugs were found; the changes are targeted and well-scoped.

  • egress credential isolation: cookie and proxy-authorization are now stripped alongside authorization/x-api-key on both the runtime-rotation-proxy and local-bridge outbound paths, with regression coverage in both test files.
  • bounded error-body reads: readErrorBody now applies a per-chunk idle timeout and a 1 MB size cap so a stalled 429/5xx body can no longer hang the handler indefinitely after headers arrive; the fallback path for non-streamable responses uses the existing withTimeout helper.
  • windows fs resilience: WAL writes, temp writes, and temp cleanup are wrapped in withFileOperationRetry; copyFileWithRetry/renameFileWithRetry and quota-cache.ts align to the shared shouldRetryFileOperation taxonomy.

Confidence Score: 4/5

safe to merge with the outstanding cleanupWal retry gap still open; the new readErrorBody logic is correct but untested at the stall/cap boundaries

the egress stripping, fs-retry taxonomy unification, and observability perms changes are all correct and well-covered. the new readErrorBody bounded-read logic is the one area where the suite doesn't exercise the timeout or overflow paths, so a silent regression there is possible. cleanupWal still lacks the retry that cleanupTemp received in this very PR, leaving a token-bearing .wal file potentially stranded on windows

lib/storage.ts (cleanupWal retry gap) and lib/runtime-rotation-proxy.ts (readErrorBody stall/cap test coverage)

Important Files Changed

Filename Overview
lib/runtime-rotation-proxy.ts adds cookie/proxy-authorization stripping in createOutboundHeaders and rewrites readErrorBody with per-chunk idle timeout + 1 MB cap; new stall-bounding logic has no dedicated unit tests for the timeout or overflow paths
lib/local-bridge.ts adds cookie and proxy-authorization deletion in forwardHeaders, matching the runtime-rotation-proxy contract; both strips are covered by regression assertions in local-bridge.test.ts
lib/storage.ts wraps WAL write, temp write, and temp cleanup in withFileOperationRetry for windows resilience; cleanupWal still lacks retry (flagged separately), and copyFileWithRetry/renameFileWithRetry now use shouldRetryFileOperation taxonomy
lib/quota-cache.ts widens RETRYABLE_FS_CODES to match the shared FILE_RETRY_CODES taxonomy (adds EAGAIN, ENOTEMPTY, EACCES); covered by new tests for EACCES-on-load and ENOTEMPTY-on-rename
lib/runtime/runtime-observability.ts snapshot written with 0o600, dir created/re-asserted with 0o700 on POSIX (no-op on win32); upgrade-path chmod handled with ENOENT guard; tested by new runtime-observability-dir-mode.test.ts
test/runtime-rotation-proxy.test.ts adds cookie and proxy-authorization null assertions for the header-stripping test; no coverage added for the new readErrorBody stall/cap paths
test/runtime-observability-dir-mode.test.ts new test verifying the 0o700 re-assert on a pre-existing permissive dir; skips on win32, uses retry-safe rmSync teardown
test/quota-cache.test.ts adds three new tests: EACCES retry on load, ENOTEMPTY retry on atomic rename, and serialized concurrent saves; all verify the widened retry taxonomy

Sequence Diagram

sequenceDiagram
    participant Client
    participant Bridge as local-bridge / rotation-proxy
    participant Upstream as OpenAI upstream

    Client->>Bridge: POST /v1/... (Authorization, x-api-key, Cookie, Proxy-Authorization)
    Note over Bridge: forwardHeaders() strips:<br/>cookie, proxy-authorization,<br/>x-api-key, authorization (inbound)
    Bridge->>Bridge: inject managed OAuth Bearer
    Bridge->>Upstream: POST /v1/... (Authorization: Bearer managed)
    Upstream-->>Bridge: HTTP 429 / 4xx / 5xx (headers arrive)
    Note over Bridge: outbound abort timer cleared on headers
    Bridge->>Bridge: readErrorBody(response, streamStallTimeoutMs)
    alt "body stalls > streamStallTimeoutMs"
        Bridge->>Bridge: reader.cancel() return partial text
    else "body > 1 MB"
        Bridge->>Bridge: break + reader.cancel() return capped text
    end
    Bridge-->>Client: proxied error response
Loading

Comments Outside Diff (2)

  1. lib/storage.ts, line 1875-1881 (link)

    P1 cleanupWal skipped the retry treatment that cleanupTemp just received, even though walPath stores the identical secret-bearing payload — the full serialised account store with refresh tokens. on windows a transient av-scanner or indexer lock on that file will silently swallow the unlink, leaving a 0o600 wal containing live tokens next to the destination indefinitely. the fix is one line, same pattern as cleanupTemp.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: lib/storage.ts
    Line: 1875-1881
    
    Comment:
    `cleanupWal` skipped the retry treatment that `cleanupTemp` just received, even though `walPath` stores the identical secret-bearing payload — the full serialised account store with refresh tokens. on windows a transient av-scanner or indexer lock on that file will silently swallow the unlink, leaving a `0o600` wal containing live tokens next to the destination indefinitely. the fix is one line, same pattern as `cleanupTemp`.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Codex

  2. lib/storage.ts, line 1875-1881 (link)

    P1 cleanupWal missing retry, token-bearing .wal can be stranded on windows

    cleanupTemp received withFileOperationRetry in this PR, but cleanupWal did not. walPath holds the identical 0o600 payload — the full serialised account store with refresh tokens. a transient AV/indexer lock on win32 silently swallows the unlink, leaving a secret-bearing .wal next to the destination indefinitely. same one-line fix as cleanupTemp.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: lib/storage.ts
    Line: 1875-1881
    
    Comment:
    **`cleanupWal` missing retry, token-bearing .wal can be stranded on windows**
    
    `cleanupTemp` received `withFileOperationRetry` in this PR, but `cleanupWal` did not. `walPath` holds the identical 0o600 payload — the full serialised account store with refresh tokens. a transient AV/indexer lock on win32 silently swallows the unlink, leaving a secret-bearing `.wal` next to the destination indefinitely. same one-line fix as `cleanupTemp`.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Codex

Fix All in Codex

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
lib/runtime-rotation-proxy.ts:899-958
**`readErrorBody` stall-bounding logic has no vitest coverage**

the per-chunk idle timeout, the 1 MB size-cap truncation, and the `withTimeout` fallback for non-streamable bodies are the most complex new code in this PR. the existing 429/401/402/5xx integration tests call through to this function but don't exercise any of those three paths — a regression that silently re-introduces an unbounded read would pass the whole suite. worth adding focused unit tests: one that stalls mid-stream and verifies early return with partial content, one that feeds >1 MB and checks truncation, and one that exercises the fallback branch with a body-less response mock.

Reviews (5): Last reviewed commit: "test(observability): assert snapshot tem..." | Re-trigger Greptile

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.
@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 2, 2026

Warning

Review limit reached

@ndycode, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 3 minutes and 10 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 25fd84e3-c394-41db-a39f-0be34ed3d8ed

📥 Commits

Reviewing files that changed from the base of the PR and between 3dcc500 and 638a207.

📒 Files selected for processing (1)
  • test/runtime-observability.test.ts
📝 Walkthrough

Walkthrough

this pr strips inbound credential headers, bounds upstream error-body reads, consolidates filesystem retry logic, tightens runtime-observability permissions, and updates tests/docs and vitest dev deps.

Changes

runtime & storage hardening

Layer / File(s) Summary
credential stripping
lib/local-bridge.ts:101-104, lib/runtime-rotation-proxy.ts:271-274, test/local-bridge.test.ts:263-275,324-335, test/runtime-rotation-proxy.test.ts:1123-1137
bridge and proxy now remove inbound cookie and proxy-authorization before forwarding upstream; tests extended to assert these headers are stripped.
defensive upstream error-body reads
lib/runtime-rotation-proxy.ts:899-954, lib/runtime-rotation-proxy.ts:1794,1819,1916,1962
readErrorBody now accepts a stall timeout and optional max bytes, prefers streaming reads with an idle timeout and size cap, falls back to timed response.text(), and is used across 429/402/403/401/5xx branches to avoid hanging reads.
storage fs-retry consolidation & quota-cache
lib/storage.ts:5,277,298-299,1835-1848,1883-1887, lib/quota-cache.ts:35-44, test/quota-cache.test.ts:433-545
shared shouldRetryFileOperation/withFileOperationRetry are imported and used for copy/rename/write/unlink paths; quota-cache retryable errno set expanded; tests added for retry and concurrent-save behavior.
runtime-observability perms & tests
lib/runtime/runtime-observability.ts:198-217,222-225, test/runtime-observability-dir-mode.test.ts:1-95, test/runtime-observability.test.ts:9,19,42,104-125
snapshot dir is created with 0o700 and temp files written with 0o600; best-effort fs.chmod on non-Windows; tests added and chmod mock wired and platform-specific behavior asserted.
docs, package, and minor test tweaks
package.json:3,164-172, .codex-plugin/plugin.json:3, AGENTS.md:6, README.md:386, docs/README.md:35, docs/releases/v2.1.13-beta.3.md:1-107, test/mcodex-statusline-scope.test.ts:3-9,85-86
release notes added/updated, vitest devDependencies bumped, package/version references updated, and test import simplified.

Sequence Diagram

sequenceDiagram
  participant client
  participant bridge as lib/local-bridge.ts
  participant proxy as lib/runtime-rotation-proxy.ts
  participant upstream
  client->>bridge: request (includes cookie / proxy-authorization)
  bridge->>proxy: forwarded request (cookie and proxy-authorization removed)
  proxy->>upstream: outbound request with managed Authorization only
  upstream-->>proxy: error response (streamable body)
  proxy->>proxy: readErrorBody(upstream, streamStallTimeoutMs, maxBytes?)
  proxy-->>client: proxied error response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

missing items (explicit):

  • missing regression tests for windows transient-lock behaviors. review lib/storage.ts:1835-1848 and lib/quota-cache.ts:35-44 and add windows-specific ci or tests for withFileOperationRetry semantics.
  • windows edge cases: ensure lib/runtime/runtime-observability.ts:198-217 skips or safely handles chmod/no-op behavior on windows and does not throw; tests partly cover this (test/runtime-observability.test.ts:104-125) but verify for edge-case failures.
  • concurrency risks around temp-file unlink and retry. review lib/storage.ts:1883-1887 for races that could leave *.tmp files or expose secret-bearing temp files.

Possibly related PRs

Suggested labels

bug

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% 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 (fix(scope): summary), is 69 chars, uses lowercase imperative, and directly summarizes the main change: pre-release hardening from deep stress test.
Description check ✅ Passed Description comprehensively covers Summary, What Changed, Tests, and Verification sections with detailed technical explanations. Includes Risk context and follow-up notes. Validation checklist present with clear test results reported (4246+ passed).
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/fs-retry-taxonomy-and-egress-hardening
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/fs-retry-taxonomy-and-egress-hardening

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 test/runtime-rotation-proxy.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: 2

Caution

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

⚠️ Outside diff range comments (4)
test/runtime-rotation-proxy.test.ts (1)

1120-1135: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

assert proxy-authorization is stripped on the proxy path.

lib/runtime-rotation-proxy.ts:271-274 strips proxy-authorization as part of the new hardening, but test/runtime-rotation-proxy.test.ts:1120-1135 only checks cookie. add proxy-authorization to this request and assert it is null upstream so this security change cannot regress silently.

as per coding guidelines, "test/**: tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior. reject changes that mock real secrets or skip assertions."

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

In `@test/runtime-rotation-proxy.test.ts` around lines 1120 - 1135, The test is
missing an assertion that the proxy strips the "proxy-authorization" header;
update the request in test/runtime-rotation-proxy.test.ts (the block that builds
the inbound request before the fetch and the expectations after) to include a
"proxy-authorization" header value and add an assertion that
calls[0]?.headers.get("proxy-authorization") is null so the hardening in
lib/runtime-rotation-proxy.ts (the proxy-authorization stripping logic) is
covered and cannot regress.
test/local-bridge.test.ts (1)

316-329: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

cover proxy-authorization stripping too.

lib/local-bridge.ts:101-104 now strips both cookie and proxy-authorization, but test/local-bridge.test.ts:316-329 only proves the cookie branch. add proxy-authorization to this request and assert it is absent on the forwarded headers so the new egress hardening is fully pinned by vitest.

as per coding guidelines, "lib/**: focus on auth rotation, windows filesystem IO, and concurrency. verify every change cites affected tests (vitest) and that new queues handle EBUSY/429 scenarios. check for logging that leaks tokens or emails."

🤖 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/local-bridge.test.ts` around lines 316 - 329, Add an assertion to the
existing test that proves the bridge strips the proxy-authorization header:
include "proxy-authorization" in the fetch request headers (alongside
"authorization", "x-api-key", and "cookie") and then assert that
headers.get("proxy-authorization") is null on the forwarded request; use the
existing variables (server.baseUrl, calls, forwarded) and the same pattern as
the existing cookie/x-api-key checks so the test pins the new stripping behavior
implemented in lib/local-bridge.ts (the code that strips cookie and
proxy-authorization).
lib/runtime-rotation-proxy.ts (1)

899-958: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

add a stalled-error-body regression.

lib/runtime-rotation-proxy.ts:899-958 changes the failure semantics for 429/402-403/401/5xx handling, but test/runtime-rotation-proxy.test.ts does not include a case that returns a never-finishing error-body stream and proves the proxy times out and returns instead of hanging. this is the core resilience contract of this change, especially for the 429 path reached from lib/runtime-rotation-proxy.ts:1793-1816.

as per coding guidelines, "lib/**: focus on auth rotation, windows filesystem IO, and concurrency. verify every change cites affected tests (vitest) and that new queues handle EBUSY/429 scenarios. check for logging that leaks tokens or emails."

🤖 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/runtime-rotation-proxy.ts` around lines 899 - 958, The change to
readErrorBody alters failure semantics by timing out stalled error-body streams
but there is no test proving the proxy doesn't hang on a never-finishing error
body (especially for the 429 path); add a vitest in
test/runtime-rotation-proxy.test.ts that mocks a Response whose
body.getReader().read() never resolves (or delays past the timeout), invokes the
proxy path that hits the 429 handling (the code that calls readErrorBody),
advances timers if using fake timers, and asserts the proxy returns the
timeout/fallback response (or an error) rather than hanging; reference
readErrorBody and the 429 handling path in lib/runtime-rotation-proxy.ts when
adding the test and ensure the test covers both stream-based bodies and the
text() fallback.
lib/storage.ts (1)

277-299: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

add vitest coverage for the widened retry taxonomy.

lib/storage.ts:277, lib/storage.ts:299, lib/storage.ts:1838, lib/storage.ts:1846, and lib/storage.ts:1887 now rely on the shared retry set instead of the older narrow checks. the reviewed patch does not include a test/... regression for eagain, enotempty, or eacces on backup copy/rename, wal writes, temp writes, or temp cleanup, so a windows lock regression here will be easy to miss. please add deterministic cases that force those fs calls through the new codes and assert the save path still completes or degrades exactly as intended.

As per coding guidelines, lib/**: "focus on auth rotation, windows filesystem IO, and concurrency. verify every change cites affected tests (vitest) and that new queues handle EBUSY/429 scenarios."

Also applies to: 1835-1887

🤖 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/storage.ts` around lines 277 - 299, Add deterministic vitest unit tests
that mock the filesystem calls exercised by the shared retry taxonomy to cover
the widened error set (eagain, enotempty, eacces and EBUSY/429) for the affected
operations: the backup copy flow (the function(s) that call
shouldRetryFileOperation and use BACKUP_COPY_MAX_ATTEMPTS /
BACKUP_COPY_BASE_DELAY_MS), renameFileWithRetry, WAL write logic, temp write
logic, and temp cleanup logic; each test should stub
fs.copyFile/fs.rename/fs.writeFile/fs.unlink/fs.rmdir (or the module wrappers
used) to throw the targeted error codes on the first N attempts and succeed (or
fail permanently) to assert the retry behavior, attempt counts, and final
outcome, using vitest fake timers or mocks to make delays deterministic and
asserting that the save path completes or degrades exactly as intended and that
EBUSY/429 are handled by the new queues.
🤖 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 `@lib/quota-cache.ts`:
- Around line 35-44: Add deterministic vitest regression tests that exercise the
expanded RETRYABLE_FS_CODES behavior and queue ordering: create one test that
stubs the function that reads the quota cache (the read path that consults
RETRYABLE_FS_CODES) to throw EAGAIN once then succeed and assert the read
retried and returned the result; create one test that stubs the rename/replace
path (the rename operation consulted against RETRYABLE_FS_CODES) to throw
ENOTEMPTY or EACCES once then succeed and assert the rename retried; and create
one concurrency test for the write queue (the enqueue/process queue functions in
quota-cache that implement backoff and last-write-wins) that uses vitest fake
timers and stubs for the underlying fs write to force a backoff on an earlier
write while a later write arrives, then assert the later write wins (final
on-disk state matches last update) and queue ordering is preserved. In all tests
use vitest mocks/spies to simulate errors by throwing Error objects with a .code
property matching "EAGAIN","ENOTEMPTY","EACCES", advance timers to drive retries
deterministically, and assert retry counts and final outcomes; reference
RETRYABLE_FS_CODES in test names/comments to tie them to the change.

In `@lib/runtime/runtime-observability.ts`:
- Around line 198-210: The mkdir call using mode 0o700 doesn't change
permissions for pre-existing dirs; after await fs.mkdir(dir, { recursive: true,
mode: 0o700 }) add an explicit await fs.chmod(dir, 0o700) wrapped in a small
retry loop to tolerate transient races (e.g., 2-3 attempts with a short backoff)
and ignore ENOENT/EPERM races where another process is concurrently
creating/removing the dir; place this immediately before writing the snapshot
(around the code that references dir and snapshot/tempPath) and log or capture
any non-transient errors. Also add a regression test that pre-creates
CODEX_MULTI_AUTH_DIR with permissive perms, runs the snapshot persistence path
(the code that writes tempPath and moves it), and then asserts the directory
mode is hardened to 0o700.

---

Outside diff comments:
In `@lib/runtime-rotation-proxy.ts`:
- Around line 899-958: The change to readErrorBody alters failure semantics by
timing out stalled error-body streams but there is no test proving the proxy
doesn't hang on a never-finishing error body (especially for the 429 path); add
a vitest in test/runtime-rotation-proxy.test.ts that mocks a Response whose
body.getReader().read() never resolves (or delays past the timeout), invokes the
proxy path that hits the 429 handling (the code that calls readErrorBody),
advances timers if using fake timers, and asserts the proxy returns the
timeout/fallback response (or an error) rather than hanging; reference
readErrorBody and the 429 handling path in lib/runtime-rotation-proxy.ts when
adding the test and ensure the test covers both stream-based bodies and the
text() fallback.

In `@lib/storage.ts`:
- Around line 277-299: Add deterministic vitest unit tests that mock the
filesystem calls exercised by the shared retry taxonomy to cover the widened
error set (eagain, enotempty, eacces and EBUSY/429) for the affected operations:
the backup copy flow (the function(s) that call shouldRetryFileOperation and use
BACKUP_COPY_MAX_ATTEMPTS / BACKUP_COPY_BASE_DELAY_MS), renameFileWithRetry, WAL
write logic, temp write logic, and temp cleanup logic; each test should stub
fs.copyFile/fs.rename/fs.writeFile/fs.unlink/fs.rmdir (or the module wrappers
used) to throw the targeted error codes on the first N attempts and succeed (or
fail permanently) to assert the retry behavior, attempt counts, and final
outcome, using vitest fake timers or mocks to make delays deterministic and
asserting that the save path completes or degrades exactly as intended and that
EBUSY/429 are handled by the new queues.

In `@test/local-bridge.test.ts`:
- Around line 316-329: Add an assertion to the existing test that proves the
bridge strips the proxy-authorization header: include "proxy-authorization" in
the fetch request headers (alongside "authorization", "x-api-key", and "cookie")
and then assert that headers.get("proxy-authorization") is null on the forwarded
request; use the existing variables (server.baseUrl, calls, forwarded) and the
same pattern as the existing cookie/x-api-key checks so the test pins the new
stripping behavior implemented in lib/local-bridge.ts (the code that strips
cookie and proxy-authorization).

In `@test/runtime-rotation-proxy.test.ts`:
- Around line 1120-1135: The test is missing an assertion that the proxy strips
the "proxy-authorization" header; update the request in
test/runtime-rotation-proxy.test.ts (the block that builds the inbound request
before the fetch and the expectations after) to include a "proxy-authorization"
header value and add an assertion that
calls[0]?.headers.get("proxy-authorization") is null so the hardening in
lib/runtime-rotation-proxy.ts (the proxy-authorization stripping logic) is
covered and cannot regress.
🪄 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: 87845a4e-31a7-4d84-8608-550830c28baa

📥 Commits

Reviewing files that changed from the base of the PR and between d0addd9 and bca3aad.

📒 Files selected for processing (8)
  • lib/local-bridge.ts
  • lib/quota-cache.ts
  • lib/runtime-rotation-proxy.ts
  • lib/runtime/runtime-observability.ts
  • lib/storage.ts
  • test/local-bridge.test.ts
  • test/mcodex-statusline-scope.test.ts
  • test/runtime-rotation-proxy.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (12)
lib/**/*.ts

📄 CodeRabbit inference engine (lib/AGENTS.md)

lib/**/*.ts: All public exports should flow through lib/index.ts or documented package subpaths
Never import from dist/ in source tests or library code
Never suppress type errors

Files:

  • lib/quota-cache.ts
  • lib/runtime/runtime-observability.ts
  • lib/local-bridge.ts
  • lib/storage.ts
  • lib/runtime-rotation-proxy.ts
{index.ts,lib/**,scripts/**}

📄 CodeRabbit inference engine (AGENTS.md)

Source code must be located in root index.ts, lib/, and scripts/ directories; dist/ is generated output and should not be edited

Files:

  • lib/quota-cache.ts
  • lib/runtime/runtime-observability.ts
  • lib/local-bridge.ts
  • lib/storage.ts
  • lib/runtime-rotation-proxy.ts
**/*.{js,ts,mjs,mts}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • lib/quota-cache.ts
  • lib/runtime/runtime-observability.ts
  • test/local-bridge.test.ts
  • lib/local-bridge.ts
  • test/mcodex-statusline-scope.test.ts
  • test/runtime-rotation-proxy.test.ts
  • lib/storage.ts
  • lib/runtime-rotation-proxy.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • lib/quota-cache.ts
  • lib/runtime/runtime-observability.ts
  • test/local-bridge.test.ts
  • lib/local-bridge.ts
  • test/mcodex-statusline-scope.test.ts
  • test/runtime-rotation-proxy.test.ts
  • lib/storage.ts
  • lib/runtime-rotation-proxy.ts
lib/**

⚙️ CodeRabbit configuration file

focus on auth rotation, windows filesystem IO, and concurrency. verify every change cites affected tests (vitest) and that new queues handle EBUSY/429 scenarios. check for logging that leaks tokens or emails.

Files:

  • lib/quota-cache.ts
  • lib/runtime/runtime-observability.ts
  • lib/local-bridge.ts
  • lib/storage.ts
  • lib/runtime-rotation-proxy.ts
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/local-bridge.test.ts
  • test/mcodex-statusline-scope.test.ts
  • test/runtime-rotation-proxy.test.ts
{scripts/**,test/**}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use bare recursive delete logic in Windows-sensitive scripts/tests without retry handling

Files:

  • test/local-bridge.test.ts
  • test/mcodex-statusline-scope.test.ts
  • test/runtime-rotation-proxy.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/local-bridge.test.ts
  • test/mcodex-statusline-scope.test.ts
  • test/runtime-rotation-proxy.test.ts
{lib/storage/**,lib/storage.ts}

📄 CodeRabbit inference engine (AGENTS.md)

Do not key project storage by worktree path; use resolveProjectStorageIdentityRoot

Files:

  • lib/storage.ts
{lib/storage/**,lib/storage.ts,lib/config.ts}

📄 CodeRabbit inference engine (AGENTS.md)

Local project-owned state defaults to ~/.codex/multi-auth; official Codex state remains under ~/.codex

Files:

  • lib/storage.ts
lib/runtime-rotation-proxy.ts

📄 CodeRabbit inference engine (lib/AGENTS.md)

lib/runtime-rotation-proxy.ts: Runtime rotation code must preserve pass-through semantics except for auth/provider headers that intentionally change
Runtime proxy client-facing headers must not expose account emails or tokens
Runtime rotation should fail open to normal official Codex forwarding when startup helpers are unavailable
Never add account emails/tokens to runtime proxy client responses

lib/runtime-rotation-proxy.ts: Do not expose account emails or tokens in runtime proxy client response headers or logs
The runtime proxy is loopback-only and uses a per-process client token, forwarding only Responses API and model discovery requests

Files:

  • lib/runtime-rotation-proxy.ts
{lib/runtime-rotation-proxy.ts,lib/codex-manager/commands/rotation.ts,lib/config.ts}

📄 CodeRabbit inference engine (AGENTS.md)

Runtime rotation is default-on through codexRuntimeRotationProxy; users can opt out with codex-multi-auth rotation disable or CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=0

Files:

  • lib/runtime-rotation-proxy.ts
🔇 Additional comments (1)
test/mcodex-statusline-scope.test.ts (1)

3-3: LGTM!

Also applies to: 85-86

Comment thread lib/quota-cache.ts
Comment thread lib/runtime/runtime-observability.ts
- 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
Copy link
Copy Markdown
Owner Author

ndycode commented Jun 2, 2026

Folded in the two follow-ups from the PR description:

Full suite 4246 passed / 1 skipped on vitest 4.1.8; typecheck + lint + audit:ci clean.

@ndycode
Copy link
Copy Markdown
Owner Author

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.

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.

Caution

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

⚠️ Outside diff range comments (1)
docs/releases/v2.1.13-beta.3.md (1)

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

fix word consistency: "prerelease" vs "pre-release".

lines 3-10 use "prerelease" in the heading and body, but line 4 uses "pre-release hardening" and line 97 repeats "pre-release hardening" in the heading. pick one variant and use it throughout.

✍️ proposed fix for consistency
 Beta prerelease that lands the Phase 1 correctness/security audit (`#499`), the new
 `mcodex` launcher + cached statusline (`#500`), and a round of pre-release
-hardening surfaced by a whole-tree stress test (`#503`). It carries forward the
+prerelease hardening surfaced by a whole-tree stress test (`#503`). It carries forward the
 cascade OAuth token-invalidation fix from v2.1.13-beta.2, the multi-workspace
 support from v2.1.13-beta.1, and the pinned-account 503 diagnostic from
 v2.1.13-beta.0.
-## Pre-release hardening (`#503`)
+## Prerelease hardening (`#503`)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/releases/v2.1.13-beta.3.md` around lines 1 - 108, The document mixes
"prerelease" and "pre-release"; standardize to a single variant (use
"prerelease") by replacing the occurrences of "pre-release hardening" and any
other "pre-release" instances with "prerelease" so the heading "Pre-release
hardening (`#503`)" and the body text (e.g., the sentence "This is a
**prerelease**. Stable `v2.1.13`..." and the phrase "pre-release hardening
surfaced by...") consistently use "prerelease".
🤖 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.

Outside diff comments:
In `@docs/releases/v2.1.13-beta.3.md`:
- Around line 1-108: The document mixes "prerelease" and "pre-release";
standardize to a single variant (use "prerelease") by replacing the occurrences
of "pre-release hardening" and any other "pre-release" instances with
"prerelease" so the heading "Pre-release hardening (`#503`)" and the body text
(e.g., the sentence "This is a **prerelease**. Stable `v2.1.13`..." and the
phrase "pre-release hardening surfaced by...") consistently use "prerelease".

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ed2a391a-1fd8-4bcb-b4f5-481a20542e0c

📥 Commits

Reviewing files that changed from the base of the PR and between bca3aad and f7e19fe.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • README.md
  • docs/README.md
  • docs/releases/v2.1.13-beta.3.md
  • package.json
📜 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 (13)
{package.json,scripts/codex-multi-auth.js,scripts/codex-routing.js}

📄 CodeRabbit inference engine (AGENTS.md)

Canonical package name is codex-multi-auth and canonical command family is codex-multi-auth ...

Files:

  • package.json
{package.json,scripts/codex.js,scripts/codex-routing.js}

📄 CodeRabbit inference engine (AGENTS.md)

The package does not publish a global codex bin; codex-multi-auth-codex is the explicit wrapper forwarding non-auth commands to official Codex

Files:

  • package.json
package.json

📄 CodeRabbit inference engine (SECURITY.md)

package.json: Pin hono to 4.12.18 or higher (but below 4.12.0-4.12.1) to avoid GHSA-xh87-mx6m-69f3 authentication bypass vulnerability
Pin rollup to ^4.59.0 or higher to avoid vulnerable versions below 4.59.0 in Vite and Vitest transitive dependencies

Files:

  • package.json
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

Files:

  • docs/releases/v2.1.13-beta.3.md
  • docs/README.md
docs/**/releases/v*.md

📄 CodeRabbit inference engine (docs/README.md)

Release notes should be organized in a releases directory with versioned files following the naming pattern releases/v[version].md, with beta releases clearly marked

Files:

  • docs/releases/v2.1.13-beta.3.md
docs/**/releases/*.md

📄 CodeRabbit inference engine (docs/README.md)

Release notes should include release-specific prerelease indicators (e.g., '-beta.3') and link to installation instructions for beta versions (e.g., npm i -g codex-multi-auth@beta)

Files:

  • docs/releases/v2.1.13-beta.3.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/releases/v2.1.13-beta.3.md
  • docs/README.md
docs/{README.md,docs/**/*.md}

📄 CodeRabbit inference engine (docs/DOCUMENTATION.md)

docs/{README.md,docs/**/*.md}: Use canonical package name codex-multi-auth in all documentation
Verify internal links in documentation are valid before merge
Ensure no conflicting guidance exists between README, docs, and governance files before merge

Files:

  • docs/README.md
docs/{README.md,docs/getting-started.md}

📄 CodeRabbit inference engine (docs/DOCUMENTATION.md)

Update README.md and docs/getting-started.md first when runtime behavior changes

Files:

  • docs/README.md
docs/{README.md,docs/getting-started.md,docs/configuration.md,docs/troubleshooting.md,docs/reference/**/*.md}

📄 CodeRabbit inference engine (docs/DOCUMENTATION.md)

Verify every documented command is executable as written before merge

Files:

  • docs/README.md
docs/{docs/**/*.md,README.md}

📄 CodeRabbit inference engine (docs/DOCUMENTATION.md)

Verify paths in documentation match runtime modules (lib/runtime-paths.ts, lib/storage.ts, lib/config.ts)

Files:

  • docs/README.md
docs/{README.md,docs/index.md,docs/README.md}

📄 CodeRabbit inference engine (docs/DOCUMENTATION.md)

Use accurate discoverability terms in public landing pages without keyword stuffing or ranking promises

Root README and docs landing pages should naturally include Codex CLI, multi-account OAuth, account switching, health checks, runtime rotation, diagnostics, and recovery when those topics are in scope

Files:

  • docs/README.md
docs/{README.md,package.json,docs/development/GITHUB_DISCOVERABILITY.md}

📄 CodeRabbit inference engine (docs/STYLE_GUIDE.md)

Keep the repository description, package description, README lead, and docs/development/GITHUB_DISCOVERABILITY.md aligned

Files:

  • docs/README.md
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:01:15.142Z
Learning: Prefer `npm i -g codex-multi-auth` for standard installation; migrate from legacy scoped prerelease `ndycode/codex-multi-auth` by uninstalling first then installing the new package
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:01:15.142Z
Learning: Use `codex-multi-auth login` to add or open the account menu; use `--device-auth` flag for headless shells and `--manual` flag only as a fallback when browser launch or callback is blocked
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:01:15.142Z
Learning: Store credentials locally under `~/.codex/multi-auth/` directory structure (override with `CODEX_MULTI_AUTH_DIR` environment variable); use project-scoped accounts under `~/.codex/multi-auth/projects/<project-key>/` for repo-specific workflows
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:01:15.142Z
Learning: Enable `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=1` (default-on) for live account rotation inside forwarded Codex CLI/app sessions; disable with `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=0` if needed
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:01:15.142Z
Learning: Enable `Responses` background mode (`CODEX_AUTH_BACKGROUND_RESPONSES=1` or `backgroundResponses` in settings) only for callers that intentionally send `background: true`, as this switches from stateless `store=false` to stateful `store=true` routing
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:01:15.142Z
Learning: Run `codex-multi-auth doctor --fix` as first troubleshooting step; use `codex-multi-auth check` and `codex-multi-auth forecast --live` to validate state after changes
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:01:15.142Z
Learning: Disable whole-pool replay by default when every account is rate-limited; use bounded outbound request budget to prevent one prompt from walking the full account pool indefinitely
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:01:15.142Z
Learning: Trigger a short cooldown instead of continuing aggressive rotation after repeated cross-account 5xx bursts; stagger proactive refresh to reduce background refresh bursts
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:01:15.142Z
Learning: Keep `codex` command owned by the official OpenAI install path; do not publish a global `codex` binary from this package
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:01:15.142Z
Learning: Use optional `codex-multi-auth-codex` forwarding wrapper only when you intentionally want this package's wrapper behavior; handle `auth ...` subcommands locally and forward all other commands to the official Codex CLI
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:01:44.161Z
Learning: Maintain comprehensive test coverage with 4,200+ passing tests; ensure `npm run audit:ci` passes clean; maintain typecheck and lint pass status
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:01:44.161Z
Learning: Support `mcodex` launcher with cached multi-auth status display showing model, reasoning effort, cwd, active account, quota usage, plan, and cache age without calling OpenAI on launch
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:01:44.161Z
Learning: Support `mcodex --tmux` flag to launch Codex inside a tmux session with mouse scrollback enabled
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:01:44.161Z
Learning: Support `mcodex --tmux --live-accounts` flag to add a live `codex-multi-auth list` monitor pane during tmux session launch
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:01:44.161Z
Learning: Support `mcodex --monitor` mode for monitor-only operation without launching Codex
🪛 LanguageTool
docs/releases/v2.1.13-beta.3.md

[uncategorized] ~4-~4: Do not mix variants of the same word (‘pre-release’ and ‘prerelease’) within a single text.
Context: ...ached statusline (#500), and a round of pre-release hardening surfaced by a whole-tree stre...

(EN_WORD_COHERENCY)


[style] ~95-~95: Consider shortening this phrase to just ‘whether’, unless you mean ‘regardless of whether’.
Context: ...-readable output, with a stable shape whether or not accounts are configured. ## Pre-releas...

(WHETHER)


[uncategorized] ~97-~97: Do not mix variants of the same word (‘pre-release’ and ‘prerelease’) within a single text.
Context: ...her or not accounts are configured. ## Pre-release hardening (#503) - Strip inbound `cook...

(EN_WORD_COHERENCY)

🔇 Additional comments (3)
README.md (1)

386-386: LGTM!

docs/README.md (1)

35-35: LGTM!

package.json (1)

3-3: LGTM!

Also applies to: 164-165, 172-172

…ins)

- 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.
@ndycode
Copy link
Copy Markdown
Owner Author

ndycode commented Jun 2, 2026

Round 2 — addressed all 3 threads (4f0eefc):

  • observability dir mode: re-assert chmod(dir, 0o700) after mkdir (mkdir's mode is a no-op on a pre-existing dir) + a POSIX-only regression that pre-creates a world-writable CODEX_MULTI_AUTH_DIR and asserts it becomes owner-only after a snapshot persist.
  • quota-cache: added read-retry (EACCES), rename-retry (ENOTEMPTY), and last-write-wins queue regressions.
  • proxy-authorization: added strip assertions on both the proxy and bridge egress tests (alongside cookie).
  • Also bumped .codex-plugin/plugin.json + AGENTS.md to beta.3 (manifest-parity test).

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

@ndycode
Copy link
Copy Markdown
Owner Author

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.

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)
test/local-bridge.test.ts (1)

240-272: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

consider adding proxy-authorization and cookie to auth-enabled path test for completeness.

the test at line 307-332 now verifies all four credential headers (authorization, x-api-key, cookie, proxy-authorization) are stripped. this auth-enabled runtime-proxy test only checks x-api-key stripping. lib/local-bridge.ts:102-115 deletes cookie and proxy-authorization unconditionally on both paths, so adding those assertions here would ensure the auth-enabled flow has equivalent regression coverage.

🔒 proposed addition to strengthen auth-enabled path coverage
 		await fetch(`${server.baseUrl}/v1/models`, {
 			headers: {
 				authorization: "Bearer inbound-client-token",
 				"x-api-key": "inbound-secret-key",
+				cookie: "session=inbound-cookie-secret",
+				"proxy-authorization": "Basic inbound-proxy-cred",
 			},
 		});

 		const forwarded = calls.find((c) => c.url.endsWith("/v1/models"));
 		const headers = new Headers(forwarded?.init?.headers as HeadersInit);
 		// The runtime key is injected as Authorization, but the inbound x-api-key is
 		// still stripped — it must never cross the bridge, runtime key or not.
 		expect(headers.get("authorization")).toBe("Bearer runtime-secret-key");
 		expect(headers.get("x-api-key")).toBeNull();
+		expect(headers.get("cookie")).toBeNull();
+		expect(headers.get("proxy-authorization")).toBeNull();
 	});
🤖 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/local-bridge.test.ts` around lines 240 - 272, The current test "strips
an inbound x-api-key on the auth-enabled runtime-proxy path" only asserts
x-api-key and authorization; update this test to also assert that
"proxy-authorization" and "cookie" are removed when forwarding to the runtime.
Locate the test function (the it(...) block) and after constructing headers from
forwarded.init.headers (variable forwarded and headers), add assertions similar
to expect(headers.get("proxy-authorization")).toBeNull() and
expect(headers.get("cookie")).toBeNull() so the auth-enabled runtime-proxy path
has parity with the other coverage.
test/runtime-observability.test.ts (1)

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

add a win32 regression for the new chmod branch.

lib/runtime/runtime-observability.ts:206-208 is now platform-specific, but test/runtime-observability-dir-mode.test.ts:71-95 skips on windows and this file only adds the mock. please add a small unit case here that forces the win32 path and asserts persistence still succeeds without calling chmod, so the new windows edge case is covered.

As per coding guidelines, "test/**: tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior."

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

In `@test/runtime-observability.test.ts` around lines 9 - 19, Add a new unit test
in this file that forces the win32 branch by temporarily stubbing
process.platform to "win32" (use Object.defineProperty and restore afterwards),
then call the same persistence routine exercised by these tests and assert the
mocked chmod (chmodMock) is not called and persistence completes successfully;
ensure the test is deterministic and restores platform and mocks after
completion so the windows-specific branch in runtime-observability (lines around
the chmod logic) is covered without invoking chmod on non-Windows hosts.
♻️ Duplicate comments (1)
lib/runtime/runtime-observability.ts (1)

202-208: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

don't swallow every chmod failure here.

on Line 206 in lib/runtime/runtime-observability.ts:206-208, this turns any chmod error into success. that means a pre-existing permissive multi-auth dir can stay open while lib/runtime/runtime-observability.ts:214-218 still writes lastAccountEmail and account ids beneath it. only ignore the expected race codes, retry transient contention, and surface the rest. test/runtime-observability-dir-mode.test.ts:71-95 covers the happy path only, so this failure mode is still untested.

🤖 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/runtime/runtime-observability.ts` around lines 202 - 208, Replace the
current blanket suppression of errors on the fs.chmod(dir, 0o700) call with
targeted handling: perform the chmod in a small retry loop (e.g., 2–3 attempts)
that retries on transient errno values like EINTR and EAGAIN/EWOULDBLOCK,
explicitly ignore the benign race errno ENOENT (dir disappeared) but rethrow or
surface any other errno (e.g., EACCES/EPERM) instead of swallowing it; update
the chmod call site that currently uses .catch(() => undefined) to implement
this logic so only expected races are ignored and real failures bubble up.
🤖 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/runtime-observability-dir-mode.test.ts`:
- Line 1: Remove the explicit Vitest import line that brings in afterEach,
beforeEach, describe, expect, it, vi from "vitest" in the test file; rely on the
configured Vitest globals instead (leave existing usages of
describe/it/expect/vi as-is) so drop the line "import { afterEach, beforeEach,
describe, expect, it, vi } from 'vitest';" and ensure there are no remaining
unused imports or references to that import.
- Around line 19-35: The local retrying remover removeDirectoryWithRetry
reimplements Windows-specific backoff and omits ENOTEMPTY; replace its usage
with the shared helper removeWithRetry by removing the removeDirectoryWithRetry
function and calling removeWithRetry(dir) where used (ensure removeWithRetry is
imported/available in the test), so the test uses the centralized Windows-safe
retry logic instead of the custom rmSync loop.

---

Outside diff comments:
In `@test/local-bridge.test.ts`:
- Around line 240-272: The current test "strips an inbound x-api-key on the
auth-enabled runtime-proxy path" only asserts x-api-key and authorization;
update this test to also assert that "proxy-authorization" and "cookie" are
removed when forwarding to the runtime. Locate the test function (the it(...)
block) and after constructing headers from forwarded.init.headers (variable
forwarded and headers), add assertions similar to
expect(headers.get("proxy-authorization")).toBeNull() and
expect(headers.get("cookie")).toBeNull() so the auth-enabled runtime-proxy path
has parity with the other coverage.

In `@test/runtime-observability.test.ts`:
- Around line 9-19: Add a new unit test in this file that forces the win32
branch by temporarily stubbing process.platform to "win32" (use
Object.defineProperty and restore afterwards), then call the same persistence
routine exercised by these tests and assert the mocked chmod (chmodMock) is not
called and persistence completes successfully; ensure the test is deterministic
and restores platform and mocks after completion so the windows-specific branch
in runtime-observability (lines around the chmod logic) is covered without
invoking chmod on non-Windows hosts.

---

Duplicate comments:
In `@lib/runtime/runtime-observability.ts`:
- Around line 202-208: Replace the current blanket suppression of errors on the
fs.chmod(dir, 0o700) call with targeted handling: perform the chmod in a small
retry loop (e.g., 2–3 attempts) that retries on transient errno values like
EINTR and EAGAIN/EWOULDBLOCK, explicitly ignore the benign race errno ENOENT
(dir disappeared) but rethrow or surface any other errno (e.g., EACCES/EPERM)
instead of swallowing it; update the chmod call site that currently uses
.catch(() => undefined) to implement this logic so only expected races are
ignored and real failures bubble up.
🪄 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: 9eecbda9-bb99-4812-af55-713c602512da

📥 Commits

Reviewing files that changed from the base of the PR and between f7e19fe and 4f0eefc.

📒 Files selected for processing (8)
  • .codex-plugin/plugin.json
  • AGENTS.md
  • lib/runtime/runtime-observability.ts
  • test/local-bridge.test.ts
  • test/quota-cache.test.ts
  • test/runtime-observability-dir-mode.test.ts
  • test/runtime-observability.test.ts
  • test/runtime-rotation-proxy.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (8)
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/runtime-rotation-proxy.test.ts
  • test/runtime-observability-dir-mode.test.ts
  • test/runtime-observability.test.ts
  • test/local-bridge.test.ts
  • test/quota-cache.test.ts
**/*.{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (AGENTS.md)

Use ESM only with "type": "module" configuration and Node >= 18

Files:

  • test/runtime-rotation-proxy.test.ts
  • test/runtime-observability-dir-mode.test.ts
  • test/runtime-observability.test.ts
  • test/local-bridge.test.ts
  • test/quota-cache.test.ts
  • lib/runtime/runtime-observability.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • test/runtime-rotation-proxy.test.ts
  • test/runtime-observability-dir-mode.test.ts
  • test/runtime-observability.test.ts
  • test/local-bridge.test.ts
  • test/quota-cache.test.ts
  • lib/runtime/runtime-observability.ts
{scripts/**/*.js,test/**/*.{ts,js}}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • test/runtime-rotation-proxy.test.ts
  • test/runtime-observability-dir-mode.test.ts
  • test/runtime-observability.test.ts
  • test/local-bridge.test.ts
  • test/quota-cache.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/runtime-rotation-proxy.test.ts
  • test/runtime-observability-dir-mode.test.ts
  • test/runtime-observability.test.ts
  • test/local-bridge.test.ts
  • test/quota-cache.test.ts
lib/**/*.ts

📄 CodeRabbit inference engine (lib/AGENTS.md)

lib/**/*.ts: All public exports should flow through lib/index.ts or documented package subpaths
Never import from dist/ in source tests or library code
Never suppress type errors

Files:

  • lib/runtime/runtime-observability.ts
lib/runtime/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Do not expose account emails or tokens in runtime proxy client response headers or logs

Runtime rotation provider id must be codex-multi-auth-runtime-proxy

Files:

  • lib/runtime/runtime-observability.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/runtime-observability.ts
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:54:44.631Z
Learning: Place source code in root `index.ts`, `lib/`, and `scripts/` directories; do not edit `dist/` or local temp/cache directories
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:54:44.631Z
Learning: Do not bypass the official Codex CLI by reimplementing general Codex commands in the wrapper
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:54:44.631Z
Learning: Keep runtime rotation default-on behavior aligned with explicit release and migration documentation
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:55:00.830Z
Learning: Store settings and account configuration in `~/.codex/multi-auth/settings.json` or use `CODEX_MULTI_AUTH_DIR` environment variable to override the root storage path
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:55:00.830Z
Learning: Credentials and OAuth tokens must remain locally stored and never transmitted outside loopback during runtime rotation
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:55:00.830Z
Learning: Runtime Responses proxy rotation is enabled by default but can be disabled via `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=0` environment variable
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:55:00.830Z
Learning: Implement account health checks with quota forecasting, automatic failover, and flagged-account recovery mechanisms
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:55:00.830Z
Learning: Maintain a usage ledger in `~/.codex/multi-auth/usage/usage-ledger.jsonl` to track local usage by project, account, and time period
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:55:00.830Z
Learning: Implement request timeout and stream stall timeout configurations via `CODEX_AUTH_FETCH_TIMEOUT_MS` and `CODEX_AUTH_STREAM_STALL_TIMEOUT_MS` environment variables
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:55:00.830Z
Learning: Disable whole-pool replay by default when every account is rate-limited to prevent excessive retry loops
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:55:00.830Z
Learning: Implement bounded outbound request budgets to prevent a single prompt from exhausting the entire account pool
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:55:00.830Z
Learning: Trigger a short cooldown on repeated cross-account 5xx bursts instead of continuing aggressive rotation
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:55:00.830Z
Learning: Stagger proactive token refresh to reduce background refresh bursts and improve performance
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:55:00.830Z
Learning: Enable `CODEX_AUTH_BACKGROUND_RESPONSES=1` only for callers that intentionally send `background: true` in requests, as it switches routing from stateless `store=false` to stateful `store=true`
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:55:00.830Z
Learning: Use TUI v2 for terminal user interface rendering, configurable via `CODEX_TUI_V2=0/1`, with color profile and glyph style customizable through `CODEX_TUI_COLOR_PROFILE` and `CODEX_TUI_GLYPHS` environment variables
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:55:00.830Z
Learning: Implement dashboard hotkey navigation with Up/Down for selection, Enter for action, 1-9 for quick switching, / for search, ? for help, and Q for cancel
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:55:00.830Z
Learning: Implement account details view hotkeys: S for set current account, R for refresh, E for enable/disable, D for delete
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:55:00.830Z
Learning: Support per-project account storage under `~/.codex/multi-auth/projects/<project-key>/openai-codex-accounts.json` for repo-specific workflows
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:55:00.830Z
Learning: Perform best-effort daily npm version checks during wrapper startup and only print manual upgrade notice on interactive TTY or when `CODEX_MULTI_AUTH_DEBUG=1`, never automatically running npm install
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:55:00.830Z
Learning: Implement settings menu with an Experimental section for staged features including preview-first sync, named local pool backup export, and refresh guard controls
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:55:00.830Z
Learning: Make settings sync operations non-destructive by default: sync previews before apply, preserve destination-only accounts, and fail safely on backup filename collisions
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:55:08.545Z
Learning: Documentation files should follow a hierarchical structure with clear sections for Getting Started, Daily Use, Release History, Repair, Reference, Maintainer Docs, and Governance
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T07:55:08.545Z
Learning: Daily-use documentation should include landing pages, FAQs, feature capability maps, configuration guidance, architecture overview, and privacy information
🔇 Additional comments (4)
.codex-plugin/plugin.json (1)

3-3: LGTM!

test/local-bridge.test.ts (1)

321-321: LGTM!

Also applies to: 331-331

test/runtime-rotation-proxy.test.ts (1)

1124-1124: LGTM!

Also applies to: 1137-1137

test/quota-cache.test.ts (1)

434-545: LGTM!

Comment thread test/runtime-observability-dir-mode.test.ts
Comment thread test/runtime-observability-dir-mode.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: 1

Caution

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

⚠️ Outside diff range comments (3)
lib/storage.ts (1)

1827-1891: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

retry wal cleanup too.

lib/storage.ts:1875 still does a single fs.unlink(walPath) after the save succeeds. that leaves the secret-bearing .wal vulnerable to the same transient windows locks you just hardened for writeJournal and cleanupTemp, so a momentary ebusy/eperm/eagain/enotempty/eacces can strand stale recovery data on disk.

proposed fix
 		cleanupWal: async () => {
 			try {
-				await fs.unlink(walPath);
+				await withFileOperationRetry(() => fs.unlink(walPath));
 			} catch {
 				// Best effort cleanup.
 			}
 		},

please also add the matching vitest regression that forces one retryable unlink failure on the wal path, otherwise this windows-specific path is still unpinned. as per coding guidelines, lib/**: "focus on auth rotation, windows filesystem IO, and concurrency. verify every change cites affected tests (vitest) and that new queues handle EBUSY/429 scenarios. check for logging that leaks tokens or emails."

🤖 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/storage.ts` around lines 1827 - 1891, The cleanupWal implementation
currently does a single fs.unlink(walPath) which can leave the secret-bearing
.wal stranded on Windows; change cleanupWal to call withFileOperationRetry(() =>
fs.unlink(walPath)) (same retry taxonomy used by writeJournal and cleanupTemp)
and preserve the existing best-effort swallow behavior on persistent failure;
then add a vitest regression that stubs/monkeypatches fs.unlink to fail once
with a retryable errno (e.g., EBUSY or EPERM) for the walPath and succeed on the
next attempt, asserting cleanupWal returns/does not throw and that no sensitive
data is logged.
test/runtime-rotation-proxy.test.ts (1)

1106-1138: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

add regression tests for bounded upstream error-body handling.

test/runtime-rotation-proxy.test.ts:1123-1137 now covers credential stripping, but there is still no direct regression for the new bounded reader in lib/runtime-rotation-proxy.ts:899-958 across error branches (lib/runtime-rotation-proxy.ts:1794, lib/runtime-rotation-proxy.ts:1819, lib/runtime-rotation-proxy.ts:1916, lib/runtime-rotation-proxy.ts:1962). please add deterministic tests for (1) stalled 4xx/5xx body timeout and (2) >1mb error-body cap. this is a concurrency/runtime hang risk path.

as per coding guidelines: test/**: tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior.

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

In `@test/runtime-rotation-proxy.test.ts` around lines 1106 - 1138, Add two
deterministic Vitest regression tests to test/runtime-rotation-proxy.test.ts
that exercise the bounded error-body reader in the proxy: (1) simulate an
upstream 4xx/5xx response that stalls while sending the error body and assert
the proxy times out the read (use createRecordingFetch or a controlled fetchImpl
that returns a ReadableStream which stops yielding; use
vi.useFakeTimers/advanceTimersToNextTimer to drive the timeout) and (2) simulate
an upstream error response that attempts to send >1MB of error bytes and assert
the proxy truncates at the 1MB cap and completes the fetch without hanging. Wire
these tests to the same helpers used in the file (startProxy, postResponses,
createRecordingFetch) and assert that the proxy returns the expected error
status/body/truncation and that upstream fetch calls complete (no pending
readers), thereby covering the bounded reader behavior used in the
runtime-rotation-proxy error branches.
test/runtime-observability.test.ts (1)

9-20: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

add assertions for the new permission hardening path.

test/runtime-observability.test.ts:9-20 adds chmodMock, but none of the cases assert the security contract introduced in lib/runtime/runtime-observability.ts:206-217: fs.chmod(dir, 0o700) on posix and fs.writeFile(..., { mode: 0o600, encoding: "utf-8" }) for the temp snapshot. as written, those lines can regress without failing this suite.

example assertions
 it("retries transient rename contention when persisting a snapshot", async () => {
   process.env.VITEST = "";
   let attempts = 0;
   renameMock.mockImplementation(async () => {
@@
   await vi.waitFor(() => {
     expect(renameMock).toHaveBeenCalledTimes(2);
   });
+  expect(chmodMock).toHaveBeenCalledWith("/mock/.codex/multi-auth", 0o700);
+  expect(writeFileMock).toHaveBeenCalledWith(
+    expect.stringContaining("runtime-observability.json."),
+    expect.any(String),
+    expect.objectContaining({ encoding: "utf-8", mode: 0o600 }),
+  );
   expect(unlinkMock).toHaveBeenCalled();
 });

as per coding guidelines, "test/**: tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior. reject changes that mock real secrets or skip assertions."

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

In `@test/runtime-observability.test.ts` around lines 9 - 20, The test added
chmodMock but never asserts the new permission-hardening behavior; update the
tests that exercise runtime-observability behavior to assert that chmodMock was
called with 0o700 for the directory (fs.chmod(dir, 0o700)) on POSIX flows and
that writeFileMock was called with options { mode: 0o600, encoding: "utf-8" }
for the temp snapshot write; locate the calls by referencing chmodMock and
writeFileMock in runtime-observability tests and add expectations (and where
needed simulate POSIX vs Windows behavior) so regressions in the fs.chmod and
fs.writeFile mode/encoding contracts will fail the suite.
♻️ Duplicate comments (1)
test/runtime-observability-dir-mode.test.ts (1)

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

use removeWithRetry here instead of a local rm loop.

test/runtime-observability-dir-mode.test.ts:19-35 reimplements windows cleanup with rmSync and only retries EBUSY/EPERM. that diverges from the shared retry taxonomy and still misses ENOTEMPTY, so this test can drift from the repo’s windows fs behavior contract.

as per coding guidelines, "Use removeWithRetry for Windows filesystem cleanup instead of bare fs.rm to handle EBUSY/EPERM/ENOTEMPTY backoff".

🤖 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/runtime-observability-dir-mode.test.ts` around lines 19 - 35, The test
defines a local rm loop in removeDirectoryWithRetry that uses rmSync and only
checks isRetriableFsError, which diverges from the shared retry taxonomy and
omits ENOTEMPTY; replace this implementation by using the shared helper
removeWithRetry (or delegate to it) so the test uses the repository-wide Windows
cleanup/backoff behavior; locate removeDirectoryWithRetry and swap its body to
call removeWithRetry(dir) (or import and call removeWithRetry) and remove
references to rmSync/isRetriableFsError to ensure the test uses the canonical
retry policy.
🤖 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 `@lib/runtime/runtime-observability.ts`:
- Around line 206-208: The current unconditional swallow of fs.chmod(dir, 0o700)
hides real failures; update the catch to only suppress the benign race case
(e.g. the path disappearing) and surface all other errors. Change the .catch(()
=> undefined) on the fs.chmod call so it inspects the caught error and
returns/ignores only when error.code indicates a transient/missing-path race
(such as 'ENOENT'), but rethrow or propagate any other error (EACCES/EPERM/etc.)
so attempts to harden CODEX_MULTI_AUTH_DIR and write runtime-observability.json
fail loudly instead of silently weakening permissions. Ensure this logic is
applied where fs.chmod(dir, 0o700) is called.

---

Outside diff comments:
In `@lib/storage.ts`:
- Around line 1827-1891: The cleanupWal implementation currently does a single
fs.unlink(walPath) which can leave the secret-bearing .wal stranded on Windows;
change cleanupWal to call withFileOperationRetry(() => fs.unlink(walPath)) (same
retry taxonomy used by writeJournal and cleanupTemp) and preserve the existing
best-effort swallow behavior on persistent failure; then add a vitest regression
that stubs/monkeypatches fs.unlink to fail once with a retryable errno (e.g.,
EBUSY or EPERM) for the walPath and succeed on the next attempt, asserting
cleanupWal returns/does not throw and that no sensitive data is logged.

In `@test/runtime-observability.test.ts`:
- Around line 9-20: The test added chmodMock but never asserts the new
permission-hardening behavior; update the tests that exercise
runtime-observability behavior to assert that chmodMock was called with 0o700
for the directory (fs.chmod(dir, 0o700)) on POSIX flows and that writeFileMock
was called with options { mode: 0o600, encoding: "utf-8" } for the temp snapshot
write; locate the calls by referencing chmodMock and writeFileMock in
runtime-observability tests and add expectations (and where needed simulate
POSIX vs Windows behavior) so regressions in the fs.chmod and fs.writeFile
mode/encoding contracts will fail the suite.

In `@test/runtime-rotation-proxy.test.ts`:
- Around line 1106-1138: Add two deterministic Vitest regression tests to
test/runtime-rotation-proxy.test.ts that exercise the bounded error-body reader
in the proxy: (1) simulate an upstream 4xx/5xx response that stalls while
sending the error body and assert the proxy times out the read (use
createRecordingFetch or a controlled fetchImpl that returns a ReadableStream
which stops yielding; use vi.useFakeTimers/advanceTimersToNextTimer to drive the
timeout) and (2) simulate an upstream error response that attempts to send >1MB
of error bytes and assert the proxy truncates at the 1MB cap and completes the
fetch without hanging. Wire these tests to the same helpers used in the file
(startProxy, postResponses, createRecordingFetch) and assert that the proxy
returns the expected error status/body/truncation and that upstream fetch calls
complete (no pending readers), thereby covering the bounded reader behavior used
in the runtime-rotation-proxy error branches.

---

Duplicate comments:
In `@test/runtime-observability-dir-mode.test.ts`:
- Around line 19-35: The test defines a local rm loop in
removeDirectoryWithRetry that uses rmSync and only checks isRetriableFsError,
which diverges from the shared retry taxonomy and omits ENOTEMPTY; replace this
implementation by using the shared helper removeWithRetry (or delegate to it) so
the test uses the repository-wide Windows cleanup/backoff behavior; locate
removeDirectoryWithRetry and swap its body to call removeWithRetry(dir) (or
import and call removeWithRetry) and remove references to
rmSync/isRetriableFsError to ensure the test uses the canonical retry policy.
🪄 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: 27c7e0fb-4921-468a-a963-7e85c7379b11

📥 Commits

Reviewing files that changed from the base of the PR and between d0addd9 and 4f0eefc.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (17)
  • .codex-plugin/plugin.json
  • AGENTS.md
  • README.md
  • docs/README.md
  • docs/releases/v2.1.13-beta.3.md
  • lib/local-bridge.ts
  • lib/quota-cache.ts
  • lib/runtime-rotation-proxy.ts
  • lib/runtime/runtime-observability.ts
  • lib/storage.ts
  • package.json
  • test/local-bridge.test.ts
  • test/mcodex-statusline-scope.test.ts
  • test/quota-cache.test.ts
  • test/runtime-observability-dir-mode.test.ts
  • test/runtime-observability.test.ts
  • test/runtime-rotation-proxy.test.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (20)
lib/**/*.ts

📄 CodeRabbit inference engine (lib/AGENTS.md)

lib/**/*.ts: All public exports should flow through lib/index.ts or documented package subpaths
Never import from dist/ in source tests or library code
Never suppress type errors

Files:

  • lib/local-bridge.ts
  • lib/quota-cache.ts
  • lib/runtime/runtime-observability.ts
  • lib/runtime-rotation-proxy.ts
  • lib/storage.ts
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

Avoid using as any, @ts-ignore, or @ts-expect-error type assertion suppressions

Files:

  • lib/local-bridge.ts
  • test/mcodex-statusline-scope.test.ts
  • test/runtime-observability.test.ts
  • test/local-bridge.test.ts
  • lib/quota-cache.ts
  • lib/runtime/runtime-observability.ts
  • test/runtime-rotation-proxy.test.ts
  • lib/runtime-rotation-proxy.ts
  • lib/storage.ts
  • test/runtime-observability-dir-mode.test.ts
  • test/quota-cache.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use ESM-only module syntax (import/export); the package is configured with "type": "module"

Files:

  • lib/local-bridge.ts
  • test/mcodex-statusline-scope.test.ts
  • test/runtime-observability.test.ts
  • test/local-bridge.test.ts
  • lib/quota-cache.ts
  • lib/runtime/runtime-observability.ts
  • test/runtime-rotation-proxy.test.ts
  • lib/runtime-rotation-proxy.ts
  • lib/storage.ts
  • test/runtime-observability-dir-mode.test.ts
  • test/quota-cache.test.ts
**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Normalize email keys using normalizeEmailKey() (trim + lowercase) for case-insensitive email deduplication

Files:

  • lib/local-bridge.ts
  • test/mcodex-statusline-scope.test.ts
  • test/runtime-observability.test.ts
  • test/local-bridge.test.ts
  • lib/quota-cache.ts
  • lib/runtime/runtime-observability.ts
  • test/runtime-rotation-proxy.test.ts
  • lib/runtime-rotation-proxy.ts
  • lib/storage.ts
  • test/runtime-observability-dir-mode.test.ts
  • test/quota-cache.test.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/local-bridge.ts
  • lib/quota-cache.ts
  • lib/runtime/runtime-observability.ts
  • lib/runtime-rotation-proxy.ts
  • lib/storage.ts
docs/{README.md,docs/**/*.md}

📄 CodeRabbit inference engine (docs/DOCUMENTATION.md)

docs/{README.md,docs/**/*.md}: Use canonical package name codex-multi-auth in all documentation
Verify internal links in documentation are valid before merge
Ensure no conflicting guidance exists between README, docs, and governance files before merge

Files:

  • docs/README.md
docs/{README.md,docs/getting-started.md}

📄 CodeRabbit inference engine (docs/DOCUMENTATION.md)

Update README.md and docs/getting-started.md first when runtime behavior changes

Files:

  • docs/README.md
docs/{README.md,docs/getting-started.md,docs/configuration.md,docs/troubleshooting.md,docs/reference/**/*.md}

📄 CodeRabbit inference engine (docs/DOCUMENTATION.md)

Verify every documented command is executable as written before merge

Files:

  • docs/README.md
docs/{docs/**/*.md,README.md}

📄 CodeRabbit inference engine (docs/DOCUMENTATION.md)

Verify paths in documentation match runtime modules (lib/runtime-paths.ts, lib/storage.ts, lib/config.ts)

Files:

  • docs/README.md
docs/{README.md,docs/index.md,docs/README.md}

📄 CodeRabbit inference engine (docs/DOCUMENTATION.md)

Use accurate discoverability terms in public landing pages without keyword stuffing or ranking promises

Root README and docs landing pages should naturally include Codex CLI, multi-account OAuth, account switching, health checks, runtime rotation, diagnostics, and recovery when those topics are in scope

Files:

  • docs/README.md
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

Documentation files should be organized with clear structure: Start Here section, Daily Use section, Release History (with Stable Releases subsection), Repair section, Reference section, Maintainer Docs section, and Governance links

Files:

  • docs/README.md
  • docs/releases/v2.1.13-beta.3.md
docs/{README.md,package.json,docs/development/GITHUB_DISCOVERABILITY.md}

📄 CodeRabbit inference engine (docs/STYLE_GUIDE.md)

Keep the repository description, package description, README lead, and docs/development/GITHUB_DISCOVERABILITY.md aligned

Files:

  • docs/README.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/README.md
  • docs/releases/v2.1.13-beta.3.md
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/runtime-observability.test.ts
  • test/local-bridge.test.ts
  • test/runtime-rotation-proxy.test.ts
  • test/runtime-observability-dir-mode.test.ts
  • test/quota-cache.test.ts
test/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Vitest for test suites; include property tests and chaos tests for critical paths

Files:

  • test/mcodex-statusline-scope.test.ts
  • test/runtime-observability.test.ts
  • test/local-bridge.test.ts
  • test/runtime-rotation-proxy.test.ts
  • test/runtime-observability-dir-mode.test.ts
  • test/quota-cache.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
  • test/runtime-observability.test.ts
  • test/local-bridge.test.ts
  • test/runtime-rotation-proxy.test.ts
  • test/runtime-observability-dir-mode.test.ts
  • test/quota-cache.test.ts
lib/runtime/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Do not expose account emails or tokens in runtime proxy client response headers or logs

Files:

  • lib/runtime/runtime-observability.ts
docs/releases/**/*.md

📄 CodeRabbit inference engine (docs/README.md)

Release notes should be organized in a releases/ directory with semantic versioning filenames (e.g., releases/v2.1.12.md, releases/v2.1.13-beta.3.md)

Files:

  • docs/releases/v2.1.13-beta.3.md
package.json

📄 CodeRabbit inference engine (SECURITY.md)

package.json: Pin hono to 4.12.18 or higher (but below 4.12.0-4.12.1) to avoid GHSA-xh87-mx6m-69f3 authentication bypass vulnerability
Pin rollup to ^4.59.0 or higher to avoid vulnerable versions below 4.59.0 in Vite and Vitest transitive dependencies

Files:

  • package.json
lib/runtime-rotation-proxy.ts

📄 CodeRabbit inference engine (lib/AGENTS.md)

lib/runtime-rotation-proxy.ts: Runtime rotation code must preserve pass-through semantics except for auth/provider headers that intentionally change
Runtime proxy client-facing headers must not expose account emails or tokens
Runtime rotation should fail open to normal official Codex forwarding when startup helpers are unavailable
Never add account emails/tokens to runtime proxy client responses

Runtime rotation proxy is loopback-only, uses per-process client tokens, and forwards only Responses API and model discovery requests

Files:

  • lib/runtime-rotation-proxy.ts
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T08:01:24.354Z
Learning: Keep runtime rotation default-on behavior (via `codexRuntimeRotationProxy`) aligned with explicit release and migration documentation; users can opt out with `codex-multi-auth rotation disable` or `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=0`
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T08:01:24.354Z
Learning: Do not bypass the official Codex CLI by reimplementing general Codex commands in the wrapper; the wrapper's primary role is account management and selective command forwarding
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T08:01:24.354Z
Learning: Node runtime requirement is >= 18; ensure all code uses features available in Node 18 or later
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T08:01:24.354Z
Learning: The canonical package name is `codex-multi-auth` and the canonical command family is `codex-multi-auth ...`; the package does not publish a global `codex` bin
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T08:01:34.577Z
Learning: This is an independent open-source project, not an official OpenAI product
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T08:01:34.577Z
Learning: Users are responsible for their own usage and policy compliance when using codex-multi-auth
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T08:01:34.577Z
Learning: For production/commercial workloads, use the OpenAI Platform API instead of OAuth account credentials through codex-multi-auth
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T08:01:34.577Z
Learning: Keep the official `codex` command owned by the official OpenAI install path; do not use codex-multi-auth to override it
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T08:01:34.577Z
Learning: Credentials should stay local and runtime rotation should be loopback-only in multi-account workflows
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T08:01:34.577Z
Learning: Use health-aware account selection with quota forecasting, automatic failover, and flagged-account recovery
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T08:01:34.577Z
Learning: Whole-pool replay should be disabled by default when every account is rate-limited
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T08:01:34.577Z
Learning: Active requests should use a bounded outbound request budget so one prompt cannot walk the full pool indefinitely
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T08:01:34.577Z
Learning: Repeated cross-account 5xx bursts should trigger a short cooldown instead of continuing aggressive rotation
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T08:01:34.577Z
Learning: Proactive refresh should be staggered to reduce background refresh bursts
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T08:01:34.577Z
Learning: Responses background mode should stay opt-in and only enabled for callers that intentionally send `background: true`
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T08:01:34.577Z
Learning: Runtime rotation should be enabled by default for request-bearing wrapper-launched Codex sessions
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T08:01:34.577Z
Learning: Installed wrappers should perform a best-effort daily npm version check during normal forwarded Codex startup
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T08:01:34.577Z
Learning: Version update notices should only print on an interactive TTY or when `CODEX_MULTI_AUTH_DEBUG=1`; never run npm install or update commands automatically
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T08:01:48.349Z
Learning: Include root-level governance links: project entry (../README.md), contribution policy (../CONTRIBUTING.md), code of conduct (../CODE_OF_CONDUCT.md), and security policy (../SECURITY.md)
🪛 LanguageTool
docs/releases/v2.1.13-beta.3.md

[uncategorized] ~4-~4: Do not mix variants of the same word (‘pre-release’ and ‘prerelease’) within a single text.
Context: ...ached statusline (#500), and a round of pre-release hardening surfaced by a whole-tree stre...

(EN_WORD_COHERENCY)


[style] ~95-~95: Consider shortening this phrase to just ‘whether’, unless you mean ‘regardless of whether’.
Context: ...-readable output, with a stable shape whether or not accounts are configured. ## Pre-releas...

(WHETHER)


[uncategorized] ~97-~97: Do not mix variants of the same word (‘pre-release’ and ‘prerelease’) within a single text.
Context: ...her or not accounts are configured. ## Pre-release hardening (#503) - Strip inbound `cook...

(EN_WORD_COHERENCY)

🔇 Additional comments (12)
.codex-plugin/plugin.json (1)

3-3: LGTM!

package.json (1)

3-3: LGTM!

Also applies to: 164-166, 172-172

AGENTS.md (1)

6-6: LGTM!

README.md (1)

386-386: LGTM!

docs/README.md (1)

35-35: LGTM!

docs/releases/v2.1.13-beta.3.md (1)

1-108: LGTM!

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

3-3: LGTM!

Also applies to: 85-86

lib/quota-cache.ts (1)

35-44: LGTM!

Also applies to: 132-145, 246-255

test/quota-cache.test.ts (1)

434-477: LGTM!

Also applies to: 479-514, 516-545

lib/local-bridge.ts (1)

101-104: LGTM!

lib/runtime-rotation-proxy.ts (1)

271-274: LGTM!

Also applies to: 899-958, 1794-1794, 1819-1819, 1916-1916, 1962-1962

test/local-bridge.test.ts (1)

320-331: LGTM!

Comment thread lib/runtime/runtime-observability.ts
…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.
@ndycode
Copy link
Copy Markdown
Owner Author

ndycode commented Jun 2, 2026

Round 3 (3dcc500): tightened the observability chmod error handling (only ENOENT is swallowed now; other failures surface instead of leaving a world-readable token dir), added a win32 no-chmod persistence test, and extended the auth-enabled bridge test to assert cookie + proxy-authorization stripping. Full suite 4250 passed / 2 skipped; typecheck + lint + audit:ci clean.

@ndycode
Copy link
Copy Markdown
Owner Author

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.

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: 1

Caution

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

⚠️ Outside diff range comments (4)
test/runtime-rotation-proxy.test.ts (1)

899-958: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

missing regression test for bounded error-body read.

the new readErrorBody at lib/runtime-rotation-proxy.ts:899-958 adds idle-timeout and 1mb cap protections, but there's no test that verifies the reader cancels and returns partial data when upstream stalls or sends an oversized error body. consider adding a test that mocks a slow/stalled error body stream and asserts the handler doesn't hang.

example scenarios to cover:

  • upstream 429 with a body that never completes → handler returns within streamStallTimeoutMs
  • upstream 5xx with body exceeding 1mb → reader stops at cap, handler continues
🤖 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/runtime-rotation-proxy.test.ts` around lines 899 - 958, Add regression
tests for readErrorBody behavior by mocking fetch responses that simulate (1) a
stalled error body and (2) an oversized (>1MB) error body; use the same test
harness helpers (createRecordingFetch / startProxy / postThreadGoal) to inject a
Response whose body is a ReadableStream that either never resolves chunks (to
trigger streamStallTimeoutMs) or yields >1MB then stops, and assert the handler
returns within streamStallTimeoutMs for the stalled case and that the returned
error body is truncated (or does not hang) for the oversized case; reference
readErrorBody and streamStallTimeoutMs in your assertions to ensure the new
timeout and cap protections are exercised.
lib/storage.ts (1)

1875-1880: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

retry wal cleanup on windows locks.

lib/storage.ts:1875 still does a single best-effort unlink on the secret-bearing wal after a successful save. the same transient eperm/ebusy/eacces/enotempty/eagain lock this patch hardens elsewhere can leave refresh tokens stranded on disk and keep recovery state stale. wrap cleanupWal in withFileOperationRetry(...), and add a vitest case that makes wal cleanup fail once before succeeding so this path stays pinned down.

suggested patch
 		cleanupWal: async () => {
 			try {
-				await fs.unlink(walPath);
+				await withFileOperationRetry(() => fs.unlink(walPath));
 			} catch {
 				// Best effort cleanup.
 			}
 		},
as per coding guidelines, `**/*.{ts,tsx,js}`: "windows filesystem safety: retry transient `EBUSY`/`EPERM`/`ENOTEMPTY` cleanup and write failures where tests cover windows locks".
🤖 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/storage.ts` around lines 1875 - 1880, The cleanupWal implementation
currently performs a single fs.unlink attempt and must be hardened against
transient Windows locks; replace the direct unlink in the cleanupWal async
function with a call to withFileOperationRetry(...) wrapping the unlink of
walPath (i.e., call withFileOperationRetry(() => fs.unlink(walPath), 'unlink',
walPath) or equivalent) so it retries on EBUSY/EPERM/ENOTEMPTY/EACCES/EAGAIN,
and add a vitest that stubs/mocks fs.unlink to fail once with a transient error
then succeed to prove the retry path executes and the wal is removed.
lib/quota-cache.ts (1)

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

retry temp cleanup after exhausted rename retries.

lib/quota-cache.ts:256 only tries unlink(tempPath) once. on windows the same lock that makes rename back off often makes cleanup fail too, so this can strand .tmp files next to the cache and leave the hardening incomplete. reuse the same retry policy here, and add a regression that makes fs.unlink throw a transient lock once.

suggested patch
 			} finally {
 				if (!renamed) {
-					try {
-						await fs.unlink(tempPath);
-					} catch {
-						// Best effort temp cleanup.
-					}
+					for (let attempt = 0; attempt < 5; attempt += 1) {
+						try {
+							await fs.unlink(tempPath);
+							break;
+						} catch (error) {
+							if (!isRetryableFsError(error) || attempt >= 4) break;
+							await sleep(10 * 2 ** attempt);
+						}
+					}
 				}
 			}
as per coding guidelines, `**/*.{ts,tsx,js}`: "windows filesystem safety: retry transient `EBUSY`/`EPERM`/`ENOTEMPTY` cleanup and write failures where tests cover windows locks".
🤖 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/quota-cache.ts` around lines 256 - 263, The final cleanup currently calls
fs.unlink(tempPath) only once when rename retries exhausted (see variables
renamed and tempPath), which can fail on Windows due to transient locks; update
the finally block to reuse the same retry policy used for rename (retry on
EBUSY/EPERM/ENOTEMPTY with short backoff) to attempt unlink multiple times
before giving up, ensure errors other than transient ones still propagate or are
logged as "Best effort temp cleanup", and add a regression test that simulates
fs.unlink throwing a transient lock error once to verify the retry behavior.
lib/runtime/runtime-observability.ts (1)

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

retry temp snapshot cleanup on windows.

lib/runtime/runtime-observability.ts:236 still does a one-shot fs.unlink(tempPath) and swallows the error. after an ebusy/eperm failure in the write/rename path, that can strand *.tmp snapshots under the same transient lock conditions this pr is hardening elsewhere. route cleanup through the shared retry taxonomy and add a vitest regression that forces unlink to fail once with a windows lock code. as per coding guidelines, "windows filesystem safety: retry transient EBUSY/EPERM/ENOTEMPTY cleanup and write failures where tests cover windows locks".

🤖 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/runtime/runtime-observability.ts` around lines 222 - 241, The finalizer
currently does a one-shot fs.unlink(tempPath) that swallows errors; change it to
use the existing retry taxonomy (RETRYABLE_SNAPSHOT_ERRORS, attempt counter,
lastError) so cleanup retries transient Windows lock errors
(EBUSY/EPERM/ENOTEMPTY) rather than silently leaving .tmp files: replace the
single await fs.unlink(tempPath) in the finally block with a retry loop that
checks error.code against RETRYABLE_SNAPSHOT_ERRORS, increments/uses attempt and
lastError like the write/rename path, and only stops retrying when non-retryable
or max attempts reached; also add a vitest regression that forces fs.unlink to
fail once with a Windows lock code and verifies the cleanup eventually succeeds.
🤖 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/runtime-observability.test.ts`:
- Around line 104-124: The test currently only checks that chmod is not called
on win32; add a POSIX-path assertion that the temp snapshot write uses
owner-only permissions by asserting writeFileMock was called with an options
object including mode: 0o600. After calling mutateRuntimeObservabilitySnapshot
and awaiting renameMock, assert (for the POSIX code path) that writeFileMock was
invoked with the expected filename and options containing mode: 0o600 (use
writeFileMock.mock.calls or toHaveBeenCalledWith/expect.objectContaining to
check the options), leaving the existing platform/win32 assertions and restore
logic intact so a regression in the hardened temp snapshot write will fail the
test.

---

Outside diff comments:
In `@lib/quota-cache.ts`:
- Around line 256-263: The final cleanup currently calls fs.unlink(tempPath)
only once when rename retries exhausted (see variables renamed and tempPath),
which can fail on Windows due to transient locks; update the finally block to
reuse the same retry policy used for rename (retry on EBUSY/EPERM/ENOTEMPTY with
short backoff) to attempt unlink multiple times before giving up, ensure errors
other than transient ones still propagate or are logged as "Best effort temp
cleanup", and add a regression test that simulates fs.unlink throwing a
transient lock error once to verify the retry behavior.

In `@lib/runtime/runtime-observability.ts`:
- Around line 222-241: The finalizer currently does a one-shot
fs.unlink(tempPath) that swallows errors; change it to use the existing retry
taxonomy (RETRYABLE_SNAPSHOT_ERRORS, attempt counter, lastError) so cleanup
retries transient Windows lock errors (EBUSY/EPERM/ENOTEMPTY) rather than
silently leaving .tmp files: replace the single await fs.unlink(tempPath) in the
finally block with a retry loop that checks error.code against
RETRYABLE_SNAPSHOT_ERRORS, increments/uses attempt and lastError like the
write/rename path, and only stops retrying when non-retryable or max attempts
reached; also add a vitest regression that forces fs.unlink to fail once with a
Windows lock code and verifies the cleanup eventually succeeds.

In `@lib/storage.ts`:
- Around line 1875-1880: The cleanupWal implementation currently performs a
single fs.unlink attempt and must be hardened against transient Windows locks;
replace the direct unlink in the cleanupWal async function with a call to
withFileOperationRetry(...) wrapping the unlink of walPath (i.e., call
withFileOperationRetry(() => fs.unlink(walPath), 'unlink', walPath) or
equivalent) so it retries on EBUSY/EPERM/ENOTEMPTY/EACCES/EAGAIN, and add a
vitest that stubs/mocks fs.unlink to fail once with a transient error then
succeed to prove the retry path executes and the wal is removed.

In `@test/runtime-rotation-proxy.test.ts`:
- Around line 899-958: Add regression tests for readErrorBody behavior by
mocking fetch responses that simulate (1) a stalled error body and (2) an
oversized (>1MB) error body; use the same test harness helpers
(createRecordingFetch / startProxy / postThreadGoal) to inject a Response whose
body is a ReadableStream that either never resolves chunks (to trigger
streamStallTimeoutMs) or yields >1MB then stops, and assert the handler returns
within streamStallTimeoutMs for the stalled case and that the returned error
body is truncated (or does not hang) for the oversized case; reference
readErrorBody and streamStallTimeoutMs in your assertions to ensure the new
timeout and cap protections are exercised.
🪄 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: 4ed44716-4fd3-455e-a638-e42c0f45bf4b

📥 Commits

Reviewing files that changed from the base of the PR and between d0addd9 and 3dcc500.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (17)
  • .codex-plugin/plugin.json
  • AGENTS.md
  • README.md
  • docs/README.md
  • docs/releases/v2.1.13-beta.3.md
  • lib/local-bridge.ts
  • lib/quota-cache.ts
  • lib/runtime-rotation-proxy.ts
  • lib/runtime/runtime-observability.ts
  • lib/storage.ts
  • package.json
  • test/local-bridge.test.ts
  • test/mcodex-statusline-scope.test.ts
  • test/quota-cache.test.ts
  • test/runtime-observability-dir-mode.test.ts
  • test/runtime-observability.test.ts
  • test/runtime-rotation-proxy.test.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (28)
!dist/**

📄 CodeRabbit inference engine (AGENTS.md)

Source code lives in root index.ts, lib/, and scripts/ directories; dist/ is generated output and should not be edited

Files:

  • AGENTS.md
  • docs/README.md
  • .codex-plugin/plugin.json
  • package.json
  • README.md
  • test/runtime-observability-dir-mode.test.ts
  • test/runtime-observability.test.ts
  • test/runtime-rotation-proxy.test.ts
  • docs/releases/v2.1.13-beta.3.md
  • lib/quota-cache.ts
  • lib/local-bridge.ts
  • test/local-bridge.test.ts
  • lib/storage.ts
  • test/quota-cache.test.ts
  • lib/runtime/runtime-observability.ts
  • lib/runtime-rotation-proxy.ts
  • test/mcodex-statusline-scope.test.ts
docs/{README.md,docs/**/*.md}

📄 CodeRabbit inference engine (docs/DOCUMENTATION.md)

docs/{README.md,docs/**/*.md}: Use canonical package name codex-multi-auth in all documentation
Verify internal links in documentation are valid before merge
Ensure no conflicting guidance exists between README, docs, and governance files before merge

Files:

  • docs/README.md
docs/{README.md,docs/getting-started.md}

📄 CodeRabbit inference engine (docs/DOCUMENTATION.md)

Update README.md and docs/getting-started.md first when runtime behavior changes

Files:

  • docs/README.md
docs/{README.md,docs/getting-started.md,docs/configuration.md,docs/troubleshooting.md,docs/reference/**/*.md}

📄 CodeRabbit inference engine (docs/DOCUMENTATION.md)

Verify every documented command is executable as written before merge

Files:

  • docs/README.md
docs/{docs/**/*.md,README.md}

📄 CodeRabbit inference engine (docs/DOCUMENTATION.md)

Verify paths in documentation match runtime modules (lib/runtime-paths.ts, lib/storage.ts, lib/config.ts)

Files:

  • docs/README.md
docs/{README.md,docs/index.md,docs/README.md}

📄 CodeRabbit inference engine (docs/DOCUMENTATION.md)

Use accurate discoverability terms in public landing pages without keyword stuffing or ranking promises

Root README and docs landing pages should naturally include Codex CLI, multi-account OAuth, account switching, health checks, runtime rotation, diagnostics, and recovery when those topics are in scope

Files:

  • docs/README.md
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

Documentation files should use .md extension and follow a hierarchical structure with clear sections for Getting Started, Daily Use, Release History, Repair, Reference, Maintainer Docs, and Governance

Cross-references in documentation should use relative markdown links (e.g., [link text](path/to/file.md)) to enable proper documentation navigation and discoverability

Files:

  • docs/README.md
  • docs/releases/v2.1.13-beta.3.md
docs/{README.md,package.json,docs/development/GITHUB_DISCOVERABILITY.md}

📄 CodeRabbit inference engine (docs/STYLE_GUIDE.md)

Keep the repository description, package description, README lead, and docs/development/GITHUB_DISCOVERABILITY.md aligned

Files:

  • docs/README.md
docs/**/{DOCUMENTATION.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,SECURITY.md,README.md}

📄 CodeRabbit inference engine (docs/README.md)

Documentation governance and contribution policies should be maintained at the project root level in files like DOCUMENTATION.md, CONTRIBUTING.md, CODE_OF_CONDUCT.md, SECURITY.md, and README.md

Files:

  • docs/README.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/README.md
  • docs/releases/v2.1.13-beta.3.md
package.json

📄 CodeRabbit inference engine (SECURITY.md)

package.json: Pin hono to 4.12.18 or higher (but below 4.12.0-4.12.1) to avoid GHSA-xh87-mx6m-69f3 authentication bypass vulnerability
Pin rollup to ^4.59.0 or higher to avoid vulnerable versions below 4.59.0 in Vite and Vitest transitive dependencies

Files:

  • package.json
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/runtime-observability-dir-mode.test.ts
  • test/runtime-observability.test.ts
  • test/runtime-rotation-proxy.test.ts
  • test/local-bridge.test.ts
  • test/quota-cache.test.ts
  • test/mcodex-statusline-scope.test.ts
**/*.{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • test/runtime-observability-dir-mode.test.ts
  • test/runtime-observability.test.ts
  • test/runtime-rotation-proxy.test.ts
  • lib/quota-cache.ts
  • lib/local-bridge.ts
  • test/local-bridge.test.ts
  • lib/storage.ts
  • test/quota-cache.test.ts
  • lib/runtime/runtime-observability.ts
  • lib/runtime-rotation-proxy.ts
  • test/mcodex-statusline-scope.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • test/runtime-observability-dir-mode.test.ts
  • test/runtime-observability.test.ts
  • test/runtime-rotation-proxy.test.ts
  • lib/quota-cache.ts
  • lib/local-bridge.ts
  • test/local-bridge.test.ts
  • lib/storage.ts
  • test/quota-cache.test.ts
  • lib/runtime/runtime-observability.ts
  • lib/runtime-rotation-proxy.ts
  • test/mcodex-statusline-scope.test.ts
{scripts/**,test/**}.{ts,tsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use bare recursive delete logic in Windows-sensitive scripts/tests without retry handling

Files:

  • test/runtime-observability-dir-mode.test.ts
  • test/runtime-observability.test.ts
  • test/runtime-rotation-proxy.test.ts
  • test/local-bridge.test.ts
  • test/quota-cache.test.ts
  • test/mcodex-statusline-scope.test.ts
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

Windows filesystem safety: retry transient EBUSY/EPERM/ENOTEMPTY cleanup and write failures where tests cover Windows locks

Files:

  • test/runtime-observability-dir-mode.test.ts
  • test/runtime-observability.test.ts
  • test/runtime-rotation-proxy.test.ts
  • lib/quota-cache.ts
  • lib/local-bridge.ts
  • test/local-bridge.test.ts
  • lib/storage.ts
  • test/quota-cache.test.ts
  • lib/runtime/runtime-observability.ts
  • lib/runtime-rotation-proxy.ts
  • 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/runtime-observability-dir-mode.test.ts
  • test/runtime-observability.test.ts
  • test/runtime-rotation-proxy.test.ts
  • test/local-bridge.test.ts
  • test/quota-cache.test.ts
  • test/mcodex-statusline-scope.test.ts
**/*{response,proxy,request,routing}*.{ts,js}

📄 CodeRabbit inference engine (README.md)

Responses background mode (background: true) should remain opt-in by default and only enable stateful store=true routing for callers that explicitly set backgroundResponses in settings or CODEX_AUTH_BACKGROUND_RESPONSES=1

Files:

  • test/runtime-rotation-proxy.test.ts
  • lib/runtime-rotation-proxy.ts
**/*{rotation,selection,retry,rate-limit}*.{ts,js}

📄 CodeRabbit inference engine (README.md)

Whole-pool replay must be disabled by default when every account is rate-limited to prevent infinite retry loops

Files:

  • test/runtime-rotation-proxy.test.ts
  • lib/runtime-rotation-proxy.ts
**/*{rotation,quota,budget,request}*.{ts,js}

📄 CodeRabbit inference engine (README.md)

Active requests must use a bounded outbound request budget to prevent a single prompt from iterating through the entire account pool indefinitely

Files:

  • test/runtime-rotation-proxy.test.ts
  • lib/quota-cache.ts
  • test/quota-cache.test.ts
  • lib/runtime-rotation-proxy.ts
**/*{rotation,error,cooldown,backoff}*.{ts,js}

📄 CodeRabbit inference engine (README.md)

Repeated cross-account 5xx bursts must trigger a short cooldown instead of continuing aggressive rotation to prevent hammering upstream services

Files:

  • test/runtime-rotation-proxy.test.ts
  • lib/runtime-rotation-proxy.ts
docs/releases/v*.md

📄 CodeRabbit inference engine (docs/README.md)

Release notes documents should follow the naming pattern releases/v{VERSION}.md for stable releases and releases/v{VERSION}-beta.{NUMBER}.md for prerelease versions

Files:

  • docs/releases/v2.1.13-beta.3.md
lib/**/*.ts

📄 CodeRabbit inference engine (lib/AGENTS.md)

lib/**/*.ts: All public exports should flow through lib/index.ts or documented package subpaths
Never import from dist/ in source tests or library code
Never suppress type errors

Files:

  • lib/quota-cache.ts
  • lib/local-bridge.ts
  • lib/storage.ts
  • lib/runtime/runtime-observability.ts
  • lib/runtime-rotation-proxy.ts
lib/**

⚙️ CodeRabbit configuration file

focus on auth rotation, windows filesystem IO, and concurrency. verify every change cites affected tests (vitest) and that new queues handle EBUSY/429 scenarios. check for logging that leaks tokens or emails.

Files:

  • lib/quota-cache.ts
  • lib/local-bridge.ts
  • lib/storage.ts
  • lib/runtime/runtime-observability.ts
  • lib/runtime-rotation-proxy.ts
**/*{storage,account,project,persist}*.{ts,js}

📄 CodeRabbit inference engine (README.md)

Account storage must support project-scoped storage under ~/.codex/multi-auth/projects/<project-key>/ for repo-specific workflows in addition to global account storage

Files:

  • lib/storage.ts
{lib/auth/**,lib/runtime/**,scripts/**}.{ts,tsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

Do not hardcode OAuth ports; use existing constants/helpers

Files:

  • lib/runtime/runtime-observability.ts
{lib/runtime/**,lib/codex-manager/commands/rotation.ts,index.ts}.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Runtime rotation is default-on through codexRuntimeRotationProxy; users can opt out with codex-multi-auth rotation disable or CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=0

Files:

  • lib/runtime/runtime-observability.ts
lib/runtime-rotation-proxy.ts

📄 CodeRabbit inference engine (lib/AGENTS.md)

lib/runtime-rotation-proxy.ts: Runtime rotation code must preserve pass-through semantics except for auth/provider headers that intentionally change
Runtime proxy client-facing headers must not expose account emails or tokens
Runtime rotation should fail open to normal official Codex forwarding when startup helpers are unavailable
Never add account emails/tokens to runtime proxy client responses

Files:

  • lib/runtime-rotation-proxy.ts
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T08:26:24.990Z
Learning: Keep runtime rotation default-on behavior aligned with explicit release and migration documentation
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T08:26:45.615Z
Learning: Credentials must stay local and never be transmitted outside the local environment; OAuth account credentials are intended for personal development use only
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T08:26:45.615Z
Learning: Runtime Responses rotation must be loopback-only; do not expose rotation proxy outside of localhost
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T08:26:45.615Z
Learning: Official Codex CLI install paths must retain ownership of the `codex` binary; do not override or patch the official `codex` command
Learnt from: CR
Repo: ndycode/codex-multi-auth

Timestamp: 2026-06-02T08:26:45.615Z
Learning: Settings validation must run after configuration changes to verify integrity; always validate with `codex-multi-auth status`, `codex-multi-auth check`, and `codex-multi-auth forecast --live`
🪛 LanguageTool
docs/releases/v2.1.13-beta.3.md

[uncategorized] ~4-~4: Do not mix variants of the same word (‘pre-release’ and ‘prerelease’) within a single text.
Context: ...ached statusline (#500), and a round of pre-release hardening surfaced by a whole-tree stre...

(EN_WORD_COHERENCY)


[style] ~95-~95: Consider shortening this phrase to just ‘whether’, unless you mean ‘regardless of whether’.
Context: ...-readable output, with a stable shape whether or not accounts are configured. ## Pre-releas...

(WHETHER)


[uncategorized] ~97-~97: Do not mix variants of the same word (‘pre-release’ and ‘prerelease’) within a single text.
Context: ...her or not accounts are configured. ## Pre-release hardening (#503) - Strip inbound `cook...

(EN_WORD_COHERENCY)

🔇 Additional comments (17)
package.json (2)

3-3: LGTM!


164-165: LGTM!

Also applies to: 172-172

.codex-plugin/plugin.json (1)

3-3: LGTM!

AGENTS.md (1)

6-6: LGTM!

README.md (1)

386-386: LGTM!

docs/README.md (1)

35-35: LGTM!

docs/releases/v2.1.13-beta.3.md (1)

1-108: LGTM!

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

3-3: LGTM!

Also applies to: 84-87

lib/local-bridge.ts (1)

101-104: LGTM!

lib/runtime-rotation-proxy.ts (3)

271-274: LGTM!


899-958: LGTM!


1794-1794: LGTM!

Also applies to: 1819-1819, 1916-1916, 1962-1962

test/local-bridge.test.ts (2)

263-275: LGTM!


324-335: LGTM!

test/runtime-rotation-proxy.test.ts (1)

1123-1137: LGTM!

test/runtime-observability-dir-mode.test.ts (2)

1-1: use vitest globals here.

test/runtime-observability-dir-mode.test.ts:1 still imports vitest globals explicitly even though *.test.ts uses the global api in this repo.


19-35: reuse removeWithRetry for cleanup.

test/runtime-observability-dir-mode.test.ts:19-35 reimplements windows delete retry with rmSync and only covers ebusy/eperm. that misses enotempty and drifts from the shared helper the test guidelines require.

Comment thread test/runtime-observability.test.ts
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.
@ndycode ndycode merged commit 1317fe9 into main Jun 2, 2026
1 of 2 checks passed
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.

1 participant