Skip to content

v2.3.0-beta.2

Pre-release
Pre-release

Choose a tag to compare

@ndycode ndycode released this 10 Jun 17:43
· 172 commits to main since this release

Runtime Rotation

Bugfixes

  • Fixed the sequential drain-first pointer advancing during a within-request fallback in legacy routing mode. persistRuntimeActiveAccount called markSwitchedLocked unconditionally when routingMutex !== "enabled", including when schedulingStrategy: "sequential" was set; a transient token-bucket fallback to a secondary account would permanently move the primary and break the #509 drain-first invariant. The same schedulingStrategy !== "sequential" guard already present in the enabled-mutex branch now applies to the legacy branch too.
  • Fixed ensureFreshAccessToken forwarding an expired access token when commitRefreshedAuthOnce returned null (account not found in storage after persist). The fallback used updatedAccount.access, which was the original stale token, causing a downstream 401 and incorrectly triggering invalidation-cooldown logic. The function now uses refreshResult.access (the just-issued token) on the null-commit path while preserving the committed account's stored token on the success path (required for the dedup-commit case where two concurrent callers share one commit).

Storage

Bugfixes

  • Fixed normalizeFlaggedStorage silently dropping workspaces, currentWorkspaceIndex, accessToken, and expiresAt from flagged accounts. The function built a hardcoded field list that omitted these fields, defeating the Zod schema's .passthrough() intent; multi-workspace accounts permanently lost their workspace list and active-workspace index after a flag→restore round-trip. All four fields are now preserved.
  • Fixed refreshQuotaCacheForMenu wiping all other accounts' quota data when a transient disk failure occurred during cache reload. The rebase-merge block had a dead catch because loadQuotaCache() never throws — it returns empty maps on any read failure. When the persisted cache came back empty, only this run's probed entries were written back, discarding every other account's still-valid quota data. The empty-load case is now detected explicitly and falls back to the full in-memory snapshot.

Security

Hardening

  • All atomic write helpers (storage.ts, recovery/storage.ts, quota-cache.ts, unified-settings.ts, and twelve other call sites) now use crypto.randomBytes for staging-path nonces instead of Math.random(), preventing a local attacker who can observe one staged path from predicting the next one (#517).
  • All GitHub Actions workflow steps are pinned to exact commit SHAs rather than floating version tags, hardening against supply-chain tag drift (#519).
  • Upstream response headers matching x-codex-multi-auth-account-* are now blocked by prefix match rather than an allowlist; a future header added under that namespace is blocked by default rather than leaking until someone extends a list (#546).
  • Fixed withTimeout rejecting after calling onTimeout: cancelling a stream reader in onTimeout can settle the raced promise with a clean done: true, and a settlement enqueued ahead of the rejection would win the race, turning a stall into a silent success. Reject is now issued before onTimeout (#546).

Refactoring

Improvements

  • Decomposed lib/codex-manager.ts across four phases: login control loop, OAuth machinery, command registry, and formatter modules extracted into lib/codex-manager/ submodules. The file shrinks from 2,266 lines to 690 lines (-82%) (#525, #535, #540, #547).
  • Decomposed lib/runtime-rotation-proxy.ts across two phases: rate-limit decision logic, stream-failover runtime, and rotation-proxy closure state extracted into lib/request/ and lib/runtime/ submodules (#532, #548).
  • Eliminated all detected import cycles; eslint-plugin-import-x no-cycle rule is now enforced in CI so regressions are caught at commit time (#541).
  • Replaced local isRecord guards in runtime-current-account.ts, codex-manager/commands/rotation.ts, and refresh-lease.ts with the canonical lib/utils.ts version. The local copies accepted arrays (typeof v === "object" && v !== null); the canonical version correctly rejects them (#544, #545).

CLI & Tests

Improvements

  • Added shared cli-test-fixtures mock factory infrastructure; all manager test suites now use a consistent set of factories, removing per-suite boilerplate (#537, #539, #550).
  • Pinned the machine-readable JSON output contract (status, report, doctor, forecast, why-selected) with snapshot tests that catch shape regressions (#533).

Build & Packaging

Improvements

  • Stripped JS source maps from the published package; declaration maps are kept for go-to-definition. Reduces published size (#527).
  • Pinned @types/node to the supported runtime floor major to prevent silent type drift from newer Node API additions (#528).
  • Raised engines.node to >=18.17.0, reflecting the actual tested minimum and aligning with undici@6 and the node18-smoke CI job (#518).
  • config/schema/config.schema.json is now generated from the Zod schema with a byte-exact drift-guard test; the schema and its source of truth can never silently diverge (#536).

Notes

  • Prerelease published under the beta dist-tag (npm i -g codex-multi-auth@beta).
  • The #509 sequential drain-first feature from 2.3.0-beta.0 is unchanged; the pointer-corruption bug above is now fixed.