Skip to content

Releases: ndycode/codex-multi-auth

v2.3.0-beta.0

04 Jun 13:06

Choose a tag to compare

v2.3.0-beta.0 Pre-release
Pre-release

Runtime Rotation

Features

  • Sequential / drain-first account scheduling (opt-in). A new
    schedulingStrategy setting (CODEX_AUTH_SCHEDULING_STRATEGY /
    schedulingStrategy, default hybrid) adds a sequential mode that drains one
    account fully before moving to the next, instead of spreading load across the
    pool. Set it to sequential to keep every new request on the current active
    account until that account is exhausted (rate-limited, cooling down, circuit-open,
    or disabled), then advance to the next usable account. When an earlier account's
    quota window recovers it reclaims the active slot on the next forward scan, so the
    pool's quota windows stagger over time rather than resetting together — longer
    uninterrupted sessions across a multi-account pool (issue #509).
  • Default behavior is unchanged. hybrid remains the default and keeps the
    existing weighted health/token/freshness selection plus per-session affinity.
    sequential is fully opt-in; pools that do not set it behave exactly as before.
  • Manual pin still wins. A switch <n> pin overrides scheduling in both modes.
    In sequential mode per-session affinity is intentionally bypassed — every
    request follows the single active account, not a per-chat sticky account.

Release Hygiene

Tests

  • Selector coverage for the drain-first path: sticky-while-usable, advance-on-
    exhaustion, wrap-to-recovered-earlier-account, returns-null when the whole pool is
    exhausted, cooldown/circuit-open/disabled failover, per-family cursor isolation,
    and the policy-blocked-anchor guard.
  • Proxy-level coverage: affinity is ignored, manual pin takes precedence, the active
    pointer advances only on true exhaustion (not on a transient attempted-this-request
    skip), and the mode survives the routing-mutex select+commit path without double-
    advancing the cursor.
  • Config coverage for schedulingStrategy: default, explicit value, env override in
    both directions, and invalid env/persisted values falling back safely.

Notes

  • Prerelease published under the beta dist-tag
    (npm i -g codex-multi-auth@beta). Whether drain-first delivers longer
    uninterrupted sessions depends on each pool's real quota-window timing, which is
    why this ships as a beta for validation on real accounts before a stable cut.

v2.2.2

03 Jun 16:14

Choose a tag to compare

Runtime Rotation

Bugfixes

  • Cleared an account's persisted runtime skip reason on its next successful request, so a reason with no time-based expiry (notably token-exhausted) no longer lingers in accountSkipReasons and keeps the forecast reporting a working account as unavailable.
  • Added recordRuntimeAccountRecovery(index) on the proxy success path: it removes the account's entry from accountSkipReasons and lastPoolExhaustionSkipReasons, is a no-op when nothing is recorded, and ignores non-integer or negative indices.

Quota & Forecast

Bugfixes

  • Stopped forecast --live marking working accounts as unavailable from a stale runtime overlay that persisted a skip reason after its window expired but was never cleared on a subsequent successful request.
  • Ignored stale time-bounded overlay reasons by cross-referencing disk state: rate-limited when getRateLimitResetTimeForFamily finds no active reset (including model-scoped keys like codex:5h), and cooling-down:... when coolingDownUntil is absent or elapsed.
  • Left non-time-bounded reasons (circuit-open, token-exhausted, policy-blocked) applied as-is by the forecast; these are cleared at the source on a successful request instead (see Runtime Rotation).
  • Aligned doctor's forecast-runtime-alignment check, which shares the forecast evaluation, so the same stale state no longer raises a spurious warning.

v2.2.1

03 Jun 12:36

Choose a tag to compare

Launcher

Bugfixes

  • Rewrote mcodex as a Node bin (scripts/mcodex.js); the old bash script shipped as a Windows bin died with HCS_E_SERVICE_NOT_AVAILABLE when a WSL stub shadowed git-bash. Zero bash dependency on the default path; tmux/watch invoked as argv arrays; graceful degrade when absent.
  • Canonicalized the direct-run gate (realpath) so the launcher runs through an npm-created symlink bin.
  • Relayed SIGTERM/SIGINT to the spawned child so it is never orphaned.

Auth

Bugfixes

  • Isolated OAuth concurrent-login state in per-call closures instead of the shared http.Server instance, so parallel logins can't cross-bind callback code/state.
  • Serialized the local-client-token store's read-modify-write through its write queue and widened the rename retry set to EBUSY/EPERM/EAGAIN/ENOTEMPTY/EACCES; debounced lastUsedAt writes on the bearer-verify hot path.

Runtime Rotation

Bugfixes

  • Closed the routingMutex="enabled" selection race: selection and cursor commit now run in one reentrant mutex acquisition. Legacy mode unchanged.
  • Bucketed a model-less /codex/responses request into the codex family (CURRENT_CODEX_MODEL) instead of the general gpt-5.5 family, keeping rotation/cooldown/budget accounting correct.
  • Stripped inbound cookie / proxy-authorization on both egress paths; short-circuited per-request storage re-reads on unchanged mtime/size; check auth before path/method (401 before 404).

Storage

Bugfixes

  • Fixed a storage-transaction deadlock: flagged-storage recovery (backup restore + legacy-file migration) inside a held lock re-acquired the global mutex and wedged all later saves. Lock ownership is now tracked so recovery persists without re-locking.
  • Preserved pinnedAccountIndex/affinityGeneration through the combined transaction clone so a doctor restore no longer erases the manual pin.
  • Serialized the env-path config save under a cross-process file lock (owner token + compare-before-unlink) plus retried stat and mtime compare-and-swap.
  • Created secret directories 0o700; floored fractional indices and coerced NaN in clampIndex.

Quota & Forecast

Improvements

  • gpt-5.5 is now the default live/quota probe model, legacy fallback chain (gpt-5.4gpt-5.3-codexgpt-5.2-codexgpt-5-codex) preserved in one shared QUOTA_PROBE_MODEL_CHAIN.
  • Codex-unavailable accounts are labeled "signed in" not "working"; live check gained Codex available / signed in only / need re-login counters (! instead of ).

Bugfixes

  • Classified the normalized "model not currently available for this ChatGPT account" wording as an entitlement block across the probe/forecast/report/check surfaces.
  • Excluded policy-blocked and token-exhausted accounts from forecast recommendations.
  • Fixed status-tone precedence so a failed live check carrying a quota percentage renders red, not green.

CLI

Bugfixes

  • Hardened --model parsing on every parser (best, forecast, report, fix, integrations, models): a flag-like or whitespace-only value after --model/-m/--model= is rejected, not consumed.
  • Rejected non-integer workspace/switch indices instead of truncating; rejected a flag-like budget check key.
  • Shipped .codex-plugin/plugin.json in the package, enforced by the pack-budget check via an exact-file requirement.
  • Read the capability matrix under the entitlement key (matching the write path); clamped out-of-range quota percentages; made capability-policy eviction LRU.

v2.2.0

02 Jun 13:09
29c8b4f

Choose a tag to compare

Launcher

Improvements

  • New mcodex launcher (#500): launches Codex with a cached status line (model, reasoning effort, cwd, active account, quota usage, plan, cache age) printed before startup. mcodex --tmux runs inside tmux with mouse scrollback; --tmux --live-accounts adds a live codex-multi-auth list pane; --monitor is monitor-only.
  • The status line reads local cache/observability/account state and never calls OpenAI on launch; refreshes quota in the background only when stale (default 10 min, CODEX_MULTI_AUTH_STATUS_QUOTA_REFRESH_INTERVAL_MS), behind a lock. Resolves the per-project pool when perProjectAccounts is on and Codex CLI sync is off. Toggle with CODEX_MULTI_AUTH_STATUSLINE=0.

Hardening

  • Validated MCODEX_MONITOR_INTERVAL / MCODEX_TMUX_HISTORY_LIMIT as numeric before interpolating into watch/tmux commands (no shell injection).
  • --monitor / --live-accounts fail fast when watch is missing instead of spawning a broken pane; status path resolves ~ correctly on Windows and never blocks the event loop.

Runtime Rotation

Bugfixes

  • Bound the rotation proxy and local bridge loopback-only with no opt-out, and stopped forwarding inbound credentials (authorization, x-api-key, cookie, proxy-authorization) upstream; IPv6 loopback normalized for bind + base URL (#499).
  • Stripped inbound cookie / proxy-authorization on both egress paths and bounded the proxy's upstream error-body read (#503).

Storage

Bugfixes

  • SHA-256-verified cached Codex instructions: a tampered or unverified-legacy cache is never fast-path served and never drives a conditional 304 (#499).
  • Validated stored ids before building filesystem paths, quarantining unsafe ids (../poison) or non-numeric time.created; rejected NUL-byte paths in resolvePath (#499, #503).
  • Made the account store atomic + self-healing (checksummed WAL + temp-and-rename) and retried the transient-lock taxonomy (EBUSY/EPERM/ENOTEMPTY/EACCES/EAGAIN) across all writes (#499).
  • Persisted runtime-observability.json owner-only (0o600 / dir 0o700) on POSIX; removed Atomics.wait sleeps from the config-load and logger retry paths (#499, #503).

Quota & Forecast

Bugfixes

  • Detected an unsupported Codex model from the upstream error detail shape (not just the nested error envelope), surfacing a friendly "Codex unavailable" note across best/forecast/report/live-check instead of leaking raw upstream text (#501/#502).
  • Kept a genuine transient failure surfacing as the real error, not masked behind the friendly note (#501/#502).
  • Bounded prompt and release-metadata fetches by connect+body timeouts that cancel a stalled body (#499).

CLI

Improvements

  • status / list gained --json with a stable shape whether or not accounts are configured (#499).

Bugfixes

  • Required a strict integer for switch <index> (no silent float truncation) (#503).
  • Masked tokens/emails across log, debug-bundle, and status sinks; the debug bundle redacts the home prefix (Windows case/separator-insensitive), strips config credentials, and masks the account id (#499).

Dependencies

Bugfixes

v2.1.13-beta.1

31 May 02:35
33e4b22

Choose a tag to compare

v2.1.13-beta.1 Pre-release
Pre-release

Accounts

Fixes

  • Account workspaces and currentWorkspaceIndex now survive a load
    round-trip. The strict V3 storage schema (AccountMetadataV3Schema) did not
    declare these fields, so Zod stripped them on every read; login captured the
    workspaces and wrote them to disk, but the next load wiped them. This is the
    root cause of workspace labels appearing empty after login and of two
    same-email accounts being indistinguishable.
  • formatAccountLabel surfaces the active workspace name, so same-email accounts
    in different workspaces stay distinguishable, for example
    Account 1 ([Personal Plus], user@gmail.com, id:g-AAAA). All prior label
    formats are preserved when no workspace is tracked.

Features

  • status / list now list every workspace beneath an account that tracks more
    than one, marking the active workspace and flagging disabled ones.
  • New workspace <account> [workspace] command: with only an account index it
    lists that account's workspaces; with a workspace index it sets the active
    workspace and persists it.
  • login --org <org_id> (and --org=<id>) binds a login to a specific
    workspace/org. It reuses the CODEX_AUTH_ACCOUNT_ID override that every login
    resolver already honors, scoped to the invocation and restored afterward, so a
    second workspace can be registered on demand instead of always resolving to the
    default org.

Release Hygiene

Tests

  • Schema round-trip preservation and a workspace-load regression that locks the
    fix in place.
  • Label disambiguation coverage and formatWorkspaceLines output coverage
    (active marker, disabled annotation, indentation).
  • Nine workspace-command cases (list, switch, persistence, already-active
    no-op, disabled rejection, out-of-range and non-numeric indices).
  • login --org argument parsing and missing-value handling.

Notes

  • The issue #486 503 root cause (doctor reports all green, runtime still returns 503) is not fixed by this prerelease.
  • Whether a real two-workspace login returns both orgs in one token or one at a time is unconfirmed. Both paths are supported (the workspace command for the former, login --org for the latter); a sanitized accounts.json from a real two-workspace account would confirm which path users hit.

v2.1.12

25 May 14:47

Choose a tag to compare

Runtime Rotation

Fixes

  • Mirrored trusted [hooks.state.*] TOML blocks from the original config.toml into runtime shadow configs when the hook key points at the user's hooks.json.
  • Preserved CRLF line endings and full hook-state table bodies while rewriting hook keys to the shadow hooks.json path.
  • Kept unrelated hook-state entries, alternate hook files, and existing shadow entries unchanged.

Release Hygiene

Tests

  • Added regression coverage for literal TOML keys, CRLF preservation, table-like content inside strings and arrays, alternate hook paths, long hook keys, shadow hooks.json visibility, and concurrent runtime shadow launches.
  • Linked duplicated TOML block scanner helpers in production and test code to reduce future drift.

v2.1.10

11 May 07:39

Choose a tag to compare

Runtime Rotation

Bugfixes

  • Runtime pool exhaustion on no-account now resets volatile runtime trackers, reloads accounts from disk once, and retries selection even when stale in-memory circuit breakers are open. This prevents a long-lived proxy from staying wedged after disk state has recovered.
  • Runtime proxy shutdown now flushes only the active account manager after a stale-state reload, avoiding stale-vs-fresh manager save races during close.
  • Pinned-account hard-fail behavior remains preserved: unavailable pinned accounts still return the explicit pinned-account error instead of silently rotating.

Diagnostics

Improvements

  • Pool-exhausted 503 responses include per-account runtime skip reasons and a targeted recovery hint.
  • report and doctor now include persisted quota-cache inputs when computing forecast readiness, so quota-exhausted cache state cannot be reported as ready by those diagnostics.
  • doctor keeps the forecast-runtime-alignment warning for disk/runtime divergence.
  • rotation reset-runtime --json no longer clears runtime diagnostics if the app bind restart fails, preserving evidence needed for follow-up diagnosis.

Release Hygiene

Improvements

  • The npm package now includes only runtime/install scripts instead of internal benchmark, audit, and maintenance scripts.
  • Production dependency patch lines were refreshed (undici and zod) while keeping the published package on the current Node 18-compatible runtime line.
  • Release index labels were corrected so older release-note rows are not shown as current stable.

v2.1.9

10 May 15:42

Choose a tag to compare

CLI

Bugfixes

  • codex-multi-auth login --device-auth no longer exits with Detected unsettled top-level await before the user can complete browser authorization. The polling sleep timer was being unref'd, which is correct for library/background callers but caused Node to exit during the foreground CLI flow. The CLI call site now opts in to a new keepAlive option on runDeviceAuthFlow so the event loop stays alive until polling resolves or the abort signal fires.
  • codex-multi-auth login --device-auth, --manual, and --no-browser now run the requested transport even when the account pool already has entries. Previously the Accounts Dashboard menu intercepted login whenever any account was present, silently ignoring the explicit flag.
  • Declining Add another account? after a successful explicit-mode add now exits cleanly. Previously the outer loginFlow loop would re-enter the same transport because the dashboard had been bypassed, trapping the user in a fresh sign-in prompt.
  • Cancelling an explicit-mode sign-in (for example pressing Ctrl-C during browser/manual auth) now exits cleanly instead of falling back to a fresh transport invocation.
  • Reaching ACCOUNT_LIMITS.MAX_ACCOUNTS (20) during an explicit-mode add now exits cleanly after printing the cap message. Previously the outer loop would silently start another sign-in session despite the cap.

Public API

Improvements

  • DeviceAuthFlowOptions adds an optional keepAlive?: boolean field. Library and background consumers retain the previous behavior (sleep timers are unref'd so polling does not hold the event loop open). Foreground CLI callers can pass keepAlive: true to keep the loop alive while polling for completion. Abort handling stays correct in both modes: clearTimeout runs in the abort listener so the timer ref is always released on cancellation.

v2.1.8

10 May 07:30

Choose a tag to compare

CLI

Improvements

  • Added codex-multi-auth unpin to clear the manual pin set by switch, bump affinityGeneration so the proxy invalidates session affinity on the next request, and resume hybrid rotation.
  • codex-multi-auth status now surfaces the pinned account index and warns when the runtime is using a different account than the pin requests.
  • codex-multi-auth switch <index> now writes pinnedAccountIndex that the proxy honors before session affinity, hybrid scoring, and pool fallback; best clears the pin.

Bugfixes

  • status no longer prints Pinned: account NaN for corrupt pins; the guard tightens to Number.isInteger, shows the raw stored value, and points at unpin.

Runtime Rotation Proxy

Bugfixes

  • Runtime rotation proxy now honors manual switch on the Codex Desktop app via a content-hash-keyed per-path read of the storage file; the pinned path is exempt from markSwitched/saveToDiskDebounced/syncCodexCliActiveSelectionForIndex.
  • Session affinity no longer locks the desktop app to one account for 20 minutes; affinityGeneration bumps from switch/unpin/best trigger SessionAffinityStore.clearAll() before chooseAccount, including for the in-flight request.
  • When the pinned account is unavailable, the proxy now returns HTTP 503 with error.code = codex_pinned_account_unavailable instead of silently rotating.
  • AccountManager.buildStorageSnapshot no longer wipes pinnedAccountIndex/affinityGeneration on routine debounced saves; it refreshes from disk before serializing.
  • Concurrent affinityGeneration increments across CLI processes are now atomic via Math.max(inMemory, disk) + 1.
  • unpin now uses saveAccountsWithRetry so a transient Windows file lock cannot silently lose the user's intent.
  • Replaced a synchronous busy-wait in readStorageMetaFromDisk with a single read plus per-path content-hash cache fallback that preserves the last snapshot on transient errors.
  • Runtime rotation proxy storage cache is now a Map<string, StorageMetaSnapshot> keyed by absolute path, fixing cross-instance and cross-worker pin leakage.

2.1.7

04 May 21:25

Choose a tag to compare

Install / Uninstall

Improvements

  • Added codex-multi-auth uninstall [--dry-run] [--json] [--clear-accounts] that reverses postinstall: unbinds Codex app rotation, removes OS launchers, strips the plugin entry from Codex.json, clears the node_modules cache, and conservatively deletes shared bun.lock only when no other Codex plugins remain.

Bugfixes

  • Removed the dead preuninstall entry from package.json because npm@7+ no longer runs it; to uninstall fully, run codex-multi-auth uninstall then npm uninstall -g codex-multi-auth. scripts/preuninstall.js is preserved and still callable via node scripts/preuninstall.js.
  • Dry-run uninstall now computes the bun.lock decision from the actual Codex.json plugin list.
  • --clear-accounts warns and exits non-zero when no handler is wired.
  • Dry-run uninstall preview no longer requires scripts/codex-app-launcher.js on disk.
  • removePluginFromList pre-filters falsy entries with list.filter(Boolean).

Core

Bugfixes

  • hydrateRuntimeEmails no longer collapses accounts sharing accountId === undefined into the same Map entry.
  • restoreTopLevelModelProvider/restoreTopLevelResponseStorage now splice missing keys ahead of the first section header instead of producing invalid TOML.
  • runFix, runHealthCheck, and forecast saveQuotaCache paths now downgrade transient EBUSY/EPERM to a partial-success warning via quotaCacheSaveError.
  • withStorageLock now initializes releaseLock as a typed no-op.

Runtime Rotation Proxy

Bugfixes

  • App-bind now requires router.state === "running" and a live PID before reusing existingState, so a dead router cannot trigger a config.toml rewrite.
  • Stream-failover read promise is now hoisted before the soft/hard timeout split.