|
1 | 1 | # v2.8.4 |
2 | 2 |
|
3 | | -A patch release. No user configuration migration is required. |
| 3 | +A corrective release. No new features and no configuration changes. |
4 | 4 |
|
5 | | -Landed as [#662](https://github.com/ndycode/codex-multi-auth/pull/662). |
| 5 | +`codex app-server` was unusable through the wrapper with runtime rotation enabled — which, since rotation is on by default, means it was unusable. On any machine that had ever run an app-server it refused to start; where it did start, every attached client drove threads against a frozen copy of the thread index. This is the third command to move onto the canonical Codex home, after the interactive TUI in v2.8.0 and `resume`/`fork` in v2.8.1, and for the same underlying reason. |
| 6 | + |
| 7 | +Closes [#659](https://github.com/ndycode/codex-multi-auth/issues/659). Landed as [#662](https://github.com/ndycode/codex-multi-auth/pull/662). |
| 8 | + |
| 9 | +Reported with a complete reproduction by [@possibilities](https://github.com/possibilities), who also sent the original patch in [#660](https://github.com/ndycode/codex-multi-auth/pull/660). |
| 10 | + |
| 11 | +## `app-server` |
| 12 | + |
| 13 | +### The server refused to start |
| 14 | + |
| 15 | +```console |
| 16 | +$ codex-multi-auth-codex --account <id> app-server --listen unix:///path/to/a.sock |
| 17 | +Error: socket directory path exists and is not a directory: \ |
| 18 | + /Users/user/.codex/multi-auth/runtime-shadow-homes/codex-multi-auth-runtime-home-XXXXXX/app-server-control |
| 19 | +``` |
| 20 | + |
| 21 | +Exit 1, no server. Codex requires `<CODEX_HOME>/app-server-control` to be a directory and checks it with a strict `lstat`. Codex itself creates that directory the first time an app-server runs, so most real homes have one — and the shadow mirror links directories rather than copying them, so the shadow home got a symlink at exactly that path. Deleting `~/.codex/app-server-control` made it start again, which is what pinned the mirror's symlink as the trigger. |
| 22 | + |
| 23 | +### A server that did start served a frozen thread index |
| 24 | + |
| 25 | +The shadow mirror snapshots `state_N.sqlite` rather than linking it. For `resume` that was [#647](https://github.com/ndycode/codex-multi-auth/issues/647): the requested thread was absent and the TUI hung. For an app-server the same divergence is worse in kind rather than degree — it is held for the entire life of the process and handed to every client that attaches, and any thread the server creates is written into a copy that is discarded at exit. |
| 26 | + |
| 27 | +Both failures have one cause, and one fix. `app-server` now takes the canonical-home transport that the bare TUI and `resume`/`fork` already use: a real `app-server-control` directory, the live thread index, and sessions that persist. Rotation stays on and travels as `-c` overrides, so the canonical `config.toml` is never rewritten ([#662](https://github.com/ndycode/codex-multi-auth/pull/662)). |
| 28 | + |
| 29 | +## Rotation helper |
| 30 | + |
| 31 | +Moving a command onto the canonical-home transport also moves it onto the shared app-helper, and three of that helper's behaviours were wrong for a resident server rather than merely suboptimal. |
| 32 | + |
| 33 | +### The app-server CLI shim is no longer installed for a wrapper-invoked server |
| 34 | + |
| 35 | +The shim exists for one purpose: letting the Codex **desktop app** have its own `codex app-server` spawn intercepted through `CODEX_CLI_PATH`. It is reachable only from the app-helper, so any command routed through that helper picks it up silently — and it stamps three things onto the environment the forwarded child inherits: |
| 36 | + |
| 37 | +| Variable | Value | Why it matters for a resident server | |
| 38 | +| --- | --- | --- | |
| 39 | +| `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY` | `0` | Codex passes its environment to shell tools and MCP servers, so every nested `codex-multi-auth-codex` or `mcodex` run reads rotation as disabled and bills whatever account the official CLI resolves. A nested `--account` fails outright. | |
| 40 | +| `CODEX_CLI_PATH` | the shim directory | The file named `codex` there is a copy of `node`. Anything resolving the CLI through this variable and running a subcommand other than `app-server` executes bare node with no script. | |
| 41 | +| `NODE_OPTIONS` | `--import=<shim>/…preload.mjs` | Inherited by every Node subprocess the server spawns — on a directory removed at wrapper exit and swept on the next helper start. | |
| 42 | + |
| 43 | +A wrapper-invoked app-server is already launched with the rotation overrides on its own command line and never needs the interception, so it no longer pays for the shim or inherits any of that. On Windows it also no longer copies the ~100 MB Node image into a per-pid directory on every launch. `codex app` and the interactive branches, which do need the shim, are unchanged. |
| 44 | + |
| 45 | +Because there is then no `CODEX_MULTI_AUTH_APP_SERVER_ACCOUNT_LABEL` in the environment, the branch requests `account/read`, `getAuthStatus`, and `account/rateLimits/read` rewriting explicitly instead of inferring it from that variable. Stdio clients see the same responses as before. |
| 46 | + |
| 47 | +### A short-lived server no longer strands its helper |
| 48 | + |
| 49 | +The helper detached on any clean exit inside a five-second window, regardless of what the caller asked for. A client that attached, ran one query, and disconnected therefore left a helper running for the full 12-hour idle timeout with nothing left to refresh its activity clock. A supervisor restarting the server stranded one per cycle. A resident server now owns its proxy for its whole lifetime and stops it on exit; the interactive TUI, which detaches deliberately, is unaffected. |
| 50 | + |
| 51 | +The window's clock also started before the helper was launched rather than after it reported ready. Launching is bounded at 15 seconds, so on a cold or loaded machine the entire grace period could elapse during startup — and `codex app`, which relies on the window rather than an explicit flag, would kill the helper it had just handed the desktop app off to. |
| 52 | + |
| 53 | +### A helper that cannot start now fails cleanly |
| 54 | + |
| 55 | +The shadow path degrades to rotation-off when its proxy will not start. The helper branches have no such shape to fall back to, and the failure surfaced as `ERR_UNHANDLED_REJECTION` with a raw stack trace, leaking the compatibility home the caller had already built. It is now a one-line diagnostic and exit 1, with the compatibility home released first. |
| 56 | + |
| 57 | +The hard failure is deliberate. A resident server that quietly loses rotation serves every attached client on whatever account the official CLI resolves — a billing error you would not see, where a failed launch is one you would. This matches how `--account` has always behaved. |
6 | 58 |
|
7 | 59 | ## Upgrade notes |
8 | 60 |
|
9 | | -No migration is required. Install the package and confirm the version: |
| 61 | +No migration is required, and no settings were added, renamed, or repurposed: |
10 | 62 |
|
11 | 63 | ```bash |
12 | 64 | npm i -g codex-multi-auth |
13 | 65 | codex-multi-auth --version |
14 | 66 | ``` |
15 | 67 |
|
16 | | -The command should report `2.8.4`. |
| 68 | +That should report `2.8.4`. Then confirm the fix against a real socket path: |
| 69 | + |
| 70 | +```bash |
| 71 | +codex-multi-auth-codex app-server --listen unix:///tmp/codex-app-server.sock |
| 72 | +``` |
| 73 | + |
| 74 | +It should start instead of exiting 1 on `app-server-control`, and attached clients should see your existing threads. |
| 75 | + |
| 76 | +Note that Codex applies the same strict `lstat` check to the listen socket's own parent directory, so `--listen unix:///tmp/x.sock` fails on macOS because `/tmp` is a symlink to `/private/tmp`. Use a real path — that behaviour is Codex's and is unrelated to this fix. |
| 77 | + |
| 78 | +Three consequences are worth knowing rather than discovering: |
| 79 | + |
| 80 | +- `app-server` no longer creates a shadow home under `<CODEX_HOME>/multi-auth/runtime-shadow-homes/`, or a shim directory under `app-server-shims/`. It reads and writes your canonical home in place, which is what makes threads persist. |
| 81 | +- Its rotation helper is stopped when the server exits rather than left to idle out. This is the opposite of the interactive TUI, and it is intentional. |
| 82 | +- A rotation proxy that cannot start now fails the server rather than silently running it unrotated. |
| 83 | + |
| 84 | +If you had been running `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=0` to get an app-server to start at all, drop it — that workaround bought a working server by turning off account rotation: |
| 85 | + |
| 86 | +```bash |
| 87 | +unset CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY |
| 88 | +codex-multi-auth rotation status |
| 89 | +``` |
| 90 | + |
| 91 | +That should report the runtime rotation proxy as enabled. |
| 92 | + |
| 93 | +## Regression coverage |
| 94 | + |
| 95 | +Five wrapper tests cover the canonical-home routing, a real `app-server-control` directory and a live thread index the server can append to, the absence of every shim side effect, helper reaping on both clean and non-zero server exits, the clean-diagnostic startup-failure path with its compatibility-home release, and `--account` propagation across the detached helper boundary. |
| 96 | + |
| 97 | +Helper reaping is asserted by polling the helper pid rather than by waiting for a shutdown marker, because Windows hard-terminates the helper and it never reaches its own cleanup handler. None of the tests shortens the idle timeout, so a stranded helper fails them instead of passing quietly. |
| 98 | + |
| 99 | +The full local suite passed with 337 test files passing and 1 skipped, for 5,364 tests passing and 4 skipped. |
0 commit comments