Skip to content

Commit be28f9b

Browse files
ndycodeclaude
andcommitted
chore: release v2.1.8
Patch release for issue #474. `codex-multi-auth switch <n>` now reliably routes the Codex desktop app to the chosen account, including mid-conversation, and `codex-multi-auth unpin` resumes hybrid rotation. Two distinct halves of the bug fixed in one cohesive change: 1. Manual `switch` had no effect on the desktop app — the runtime rotation proxy ignored `storage.activeIndex` and clobbered the selection on every successful request. Fixed by introducing an explicit `pinnedAccountIndex` field on storage and a pin-first branch in `chooseAccount` that fires before session affinity, hybrid scoring, and pool fallback. The pinned path is exempt from `markSwitched` so the proxy never clobbers its own pin. New `unpin` subcommand clears the pin; `best` clears it as well. Pinned account unavailable returns HTTP 503 `codex_pinned_account_unavailable` instead of silently rotating. 2. Session affinity locked the desktop app to one account for up to 20 minutes — the Codex desktop app chains turns via `previous_response_id`. Fixed via a new `affinityGeneration` counter on storage that `switch`/`unpin`/`best` bump and the proxy observes via a content-hash-keyed per-path read. When the disk generation exceeds memory, the proxy calls `SessionAffinityStore.clearAll()` before `chooseAccount` so the same in-flight request benefits. Hardening from review: - `AccountManager.buildStorageSnapshot` now persists pin/gen (was silently wiping on every routine debounced save). - `unpin` uses `saveAccountsWithRetry` for Windows EBUSY/EPERM resilience. - Atomic `affinityGeneration` increments via Math.max(inMemory, disk)+1 so concurrent CLI processes can't lose an invalidation signal. - Per-path content-hash cache in the proxy replaces mtime-only key. - Hot-path spin-wait removed from `readStorageMetaFromDisk`. - `Number.isInteger` guard + raw-value display in status output. - `PersistedSwitchReasonSchema` as single source for switch-reason union. Validated with typecheck, lint, full vitest suite (3974 tests passing across 267 files), local build, and CLI smoke tests on Windows (--version, --help, status, unpin, rotation status). See docs/releases/v2.1.8.md for full release notes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 62f7a41 commit be28f9b

5 files changed

Lines changed: 49 additions & 6 deletions

File tree

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -383,8 +383,9 @@ codex-multi-auth doctor --json
383383

384384
## Release Notes
385385

386-
- Current stable: [docs/releases/v2.1.7.md](docs/releases/v2.1.7.md)
387-
- Previous stable: [docs/releases/v2.1.6.md](docs/releases/v2.1.6.md)
386+
- Current stable: [docs/releases/v2.1.8.md](docs/releases/v2.1.8.md)
387+
- Previous stable: [docs/releases/v2.1.7.md](docs/releases/v2.1.7.md)
388+
- Earlier stable: [docs/releases/v2.1.6.md](docs/releases/v2.1.6.md)
388389
- Earlier stable: [docs/releases/v2.1.5.md](docs/releases/v2.1.5.md)
389390
- Earlier stable: [docs/releases/v2.1.4.md](docs/releases/v2.1.4.md)
390391
- Earlier stable: [docs/releases/v2.1.3.md](docs/releases/v2.1.3.md)

docs/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ Public documentation for the `codex-multi-auth` Codex CLI multi-account OAuth ma
3232

3333
| Document | Focus |
3434
| --- | --- |
35-
| [releases/v2.1.7.md](releases/v2.1.7.md) | Current stable release notes |
36-
| [releases/v2.1.6.md](releases/v2.1.6.md) | Prior stable release notes |
35+
| [releases/v2.1.8.md](releases/v2.1.8.md) | Current stable release notes |
36+
| [releases/v2.1.7.md](releases/v2.1.7.md) | Prior stable release notes |
37+
| [releases/v2.1.6.md](releases/v2.1.6.md) | Earlier stable release notes |
3738
| [releases/v2.1.5.md](releases/v2.1.5.md) | Earlier stable release notes |
3839
| [releases/v2.1.4.md](releases/v2.1.4.md) | Earlier stable release notes |
3940
| [releases/v2.1.3.md](releases/v2.1.3.md) | Earlier stable release notes |

docs/releases/v2.1.8.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# v2.1.8 — manual switch reaches the Codex desktop app
2+
3+
Patch release for [issue #474](https://github.com/ndycode/codex-multi-auth/issues/474). `codex-multi-auth switch <n>` now reliably routes the Codex desktop app to the chosen account, including mid-conversation, and `codex-multi-auth unpin` resumes hybrid rotation.
4+
5+
## Bug Fixes
6+
7+
- **Manual `switch` had no effect on the desktop app.** The runtime rotation proxy loaded accounts once at startup, ignored `storage.activeIndex`, and overwrote the manual selection on every successful request via `markSwitched("rotation")`. The proxy now honors a new `pinnedAccountIndex` field that `switch` writes to storage. The pin branch fires before session affinity, hybrid scoring, and pool fallback in `chooseAccount`. The pinned path is exempt from `markSwitched` / `saveToDiskDebounced` / `syncCodexCliActiveSelectionForIndex` so the proxy never clobbers the pin it is honoring. (#474)
8+
- **Session affinity locked the desktop app to one account for up to 20 minutes.** The Codex desktop app chains chat turns via `previous_response_id`, which the proxy uses as a stable session key. With default-on session affinity (20-minute TTL), every turn of a chat glued to whichever account first responded — even after `switch`, `unpin`, or `best`. A new `affinityGeneration` counter on storage is bumped by `switch` / `unpin` / `best` and observed by the proxy via a content-hash-keyed per-path read; when the disk generation exceeds the proxy's in-memory tracker, `SessionAffinityStore.clearAll()` runs before `chooseAccount` so the same in-flight request benefits. (#474)
9+
- **`AccountManager.buildStorageSnapshot` silently wiped `pinnedAccountIndex` and `affinityGeneration` on every routine debounced save** (rate-limit, cooldown, near-quota refund). Snapshot now refreshes pin/gen from disk just before serializing and only adopts the disk pin when disk `affinityGeneration` strictly exceeds memory, matching the CLI's bump-then-write contract. (#474)
10+
- **Pinned account is hard-failed, not silently rotated.** When the pinned account is rate-limited, cooling down, disabled, or blocked by policy, the proxy returns HTTP 503 with `code: "codex_pinned_account_unavailable"` and a `pinnedAccountIndex` field rather than falling through to rotation. (#474)
11+
- **Atomic `affinityGeneration` increments across concurrent CLI processes.** `persistAndSyncSelectedAccount` and `unpin` re-read the on-disk generation just before save and use `Math.max(inMemory, disk) + 1`, so two concurrent `switch`/`unpin`/`best` invocations cannot lose an invalidation signal. (#474)
12+
- **`unpin` now uses `saveAccountsWithRetry`.** Matches every other mutation path in the codebase; absorbs Windows EBUSY/EPERM transient writer contention so the user's intent isn't silently lost. (#474)
13+
- **Hot-path spin-wait in `readStorageMetaFromDisk` removed.** Earlier hardening introduced a `while (Date.now() < deadline) {}` busy-wait between transient-FS retries on the proxy's request hot path. Replaced with a single read + per-path content-hash cache fallback; transient errors (`EBUSY`/`EPERM`/`EACCES`/`EAGAIN`/`SyntaxError` from a partial-write rename) preserve the last cached snapshot rather than serving defaults. (#474)
14+
- **Status command bounds-check.** `codex-multi-auth status` now uses `Number.isInteger` (rejecting `NaN` and non-integer floats) and prints the raw stored value when the pin is out of range, with a remediation hint pointing at `codex-multi-auth unpin`. (#474)
15+
16+
## New Features
17+
18+
- New `codex-multi-auth unpin` command. Idempotent clear of the manual pin set by `switch`; bumps `affinityGeneration` so the proxy resumes hybrid rotation on the next desktop-app request. Wired through the dispatcher and surfaced in canonical help output. (#474)
19+
- `codex-multi-auth status` surfaces the pinned account index and warns when the runtime is currently using a different account than the pin requests. (#474)
20+
21+
## HTTP/Error Mapping
22+
23+
- New error code `codex_pinned_account_unavailable` (HTTP 503) added to the Runtime Rotation Proxy Error Contract in `docs/reference/error-contracts.md`. Response includes a `pinnedAccountIndex` field. (#474)
24+
25+
## Documentation
26+
27+
- Documented the new pin contract in `docs/reference/commands.md` (Daily Use table + Upgrade Notes) and in canonical CLI help.
28+
- Documented `codex_pinned_account_unavailable` in `docs/reference/error-contracts.md`.
29+
- Added `v2.1.8` release notes (this file) and refreshed the docs portal and root README release-history links.
30+
31+
## Chores
32+
33+
- Introduced `PersistedSwitchReasonSchema` in `lib/schemas.ts` as the single source for the CLI persist switch-reason union. `lib/codex-manager.ts` and `lib/codex-manager/commands/switch.ts` now import the inferred `PersistedSwitchReason` type instead of re-declaring the literal union, eliminating type-contract drift.
34+
- Added 78 new behavioral tests across `test/issue-474-pin-honored.test.ts` (20), `test/issue-474-affinity-invalidation.test.ts` (19), `test/issue-474-pin-end-to-end.test.ts` (real `http.Server` + `http.request`), and `test/issue-474-pin-safety.test.ts` (19). Coverage includes pin write/clear, pin-priority over hybrid scoring + session affinity, hard-fail on unavailable pin, no-clobber assertions, content-hash cache invalidation, transient FS error preservation, per-path cache isolation, monotonic concurrent gen bumps, atomic increment across CLI processes, `buildStorageSnapshot` round-trip + race protection, and direct EBUSY coverage for `readPinAndGenFromDisk`.
35+
- Validated the release with typecheck, lint, and the full vitest suite (3974 tests passing across 267 files).
36+
37+
## Changelog
38+
39+
Full Changelog: https://github.com/ndycode/codex-multi-auth/compare/v2.1.7...v2.1.8
40+
41+
- #475 fix: honor manual switch as pinned account in proxy and invalidate session affinity (#474) @ndycode

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codex-multi-auth",
3-
"version": "2.1.7",
3+
"version": "2.1.8",
44
"description": "Codex CLI multi-account OAuth manager with account switching, health checks, runtime rotation, diagnostics, and recovery tools for @openai/codex",
55
"main": "./dist/index.js",
66
"types": "./dist/index.d.ts",

0 commit comments

Comments
 (0)