Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
dde2472
Add runtime rotation config commands
ndycode Apr 24, 2026
daa3716
Add runtime Responses rotation proxy
ndycode Apr 24, 2026
09cec82
Wire Codex wrapper to runtime proxy
ndycode Apr 24, 2026
9670d7f
Document runtime rotation proxy
ndycode Apr 24, 2026
f22c96b
Authenticate runtime rotation proxy clients
ndycode Apr 24, 2026
c2c1ec6
Add automatic app runtime rotation
ndycode Apr 24, 2026
dfabd42
Install managed Codex app launcher
ndycode Apr 24, 2026
a0c281d
Clarify app launcher routing limits
ndycode Apr 24, 2026
955f8d0
Expose runtime rotation account status
ndycode Apr 24, 2026
dffbe2e
feat: bind Codex app runtime rotation
ndycode Apr 24, 2026
3ec7e6e
Fix runtime rotation app bind
ndycode Apr 24, 2026
324ca07
Fix runtime rotation state sync
ndycode Apr 24, 2026
98bd520
Preserve Codex root state in runtime mirror
ndycode Apr 25, 2026
0706cbf
Fix runtime rotation review regressions
ndycode Apr 25, 2026
125b78b
Tighten runtime review fixes
ndycode Apr 25, 2026
914ad73
Harden app bind router token state
ndycode Apr 25, 2026
c1330fc
Recover stale shadow home sync locks
ndycode Apr 25, 2026
dca7ef6
Centralize app helper status constant
ndycode Apr 25, 2026
9725080
Recover orphaned shadow sync locks
ndycode Apr 25, 2026
17ce1d2
Address runtime rotation review hardening
ndycode Apr 25, 2026
0f23ecf
Fix app helper lifecycle review issues
ndycode Apr 25, 2026
1752411
Fix remaining runtime review findings
ndycode Apr 25, 2026
7a3dc67
Fix app helper review findings
ndycode Apr 25, 2026
ef45d7d
Preserve concurrent shadow sync updates
ndycode Apr 25, 2026
1c7540d
Harden app router review gaps
ndycode Apr 25, 2026
14e3c18
Fix auth command wrapper routing
ndycode Apr 25, 2026
6110f65
Fix stale shadow sync lock retry edge
ndycode Apr 25, 2026
4ed2bba
Fix post-runtime Codex active selection sync
ndycode Apr 25, 2026
4617901
Fix app helper EPERM owner liveness
ndycode Apr 25, 2026
d156018
Clean up app helper shim directories
ndycode Apr 25, 2026
9e7bf5d
Harden app rotation auth surfaces
ndycode Apr 25, 2026
b22f79e
Harden app router and launcher review fixes
ndycode Apr 25, 2026
503ba9c
Fix shadow sync and proxy error review findings
ndycode Apr 25, 2026
c781818
Fix Greptile runtime rotation cleanup items
ndycode Apr 25, 2026
61472eb
Harden runtime rotation shutdown and sync
ndycode Apr 25, 2026
7de04e0
Fix concurrent shadow sync and shim cleanup
ndycode Apr 25, 2026
b9bc178
Fix runtime rotation review regressions
ndycode Apr 25, 2026
85d9c94
Fix app router review regressions
ndycode Apr 25, 2026
4c80bbb
Strip expect header in runtime proxy
ndycode Apr 25, 2026
55596f7
Fix shadow sync orphan lock wait
ndycode Apr 25, 2026
9ff882c
Fix runtime rotation review issues
ndycode Apr 25, 2026
5ef5796
Resume stdin after app protocol cleanup
ndycode Apr 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,10 @@ If browser launch is blocked, use the alternate login paths in [docs/getting-sta

| Command | What it answers |
| --- | --- |
| `codex auth report --live --json` | How do I get the full machine-readable health report? |
| `codex auth fix --live --model gpt-5-codex` | How do I run live repair probes with a chosen model? |
| `codex auth why-selected --json` | Which account does the selector pick now, and why? |
| `codex auth report --live --json` | How do I get the full machine-readable health report? |
| `codex auth fix --live --model gpt-5-codex` | How do I run live repair probes with a chosen model? |
| `codex auth why-selected --json` | Which account does the selector pick now, and why? |
| `codex auth rotation status` | Is live runtime account rotation enabled for forwarded Codex sessions? |

### Reliability behavior

Expand Down Expand Up @@ -230,9 +231,13 @@ Selected runtime/environment overrides:
| Variable | Effect |
| --- | --- |
| `CODEX_MULTI_AUTH_DIR` | Override settings/accounts root |
| `CODEX_MULTI_AUTH_CONFIG_PATH` | Alternate config file path |
| `CODEX_MODE=0/1` | Disable/enable Codex mode |
| `CODEX_TUI_V2=0/1` | Disable/enable TUI v2 |
| `CODEX_MULTI_AUTH_CONFIG_PATH` | Alternate config file path |
| `CODEX_MODE=0/1` | Disable/enable Codex mode |
| `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=0/1` | Opt in/out of live Responses proxy rotation for forwarded Codex CLI/app sessions |
| `CODEX_MULTI_AUTH_APP_ROTATION_IDLE_MS=<ms>` | Override automatic Codex app helper idle shutdown |
| `CODEX_MULTI_AUTH_APP_BIND_INSTALL=0/1` | Opt out/in of packaged Codex app bind self-heal during install/update or rotation enable |
| `CODEX_MULTI_AUTH_APP_LAUNCHER_INSTALL=0/1` | Opt out/in of routing supported app shortcuts during rotation enable |
| `CODEX_TUI_V2=0/1` | Disable/enable TUI v2 |
| `CODEX_TUI_COLOR_PROFILE=truecolor|ansi256|ansi16` | TUI color profile |
| `CODEX_TUI_GLYPHS=ascii|unicode|auto` | TUI glyph style |
| `CODEX_AUTH_BACKGROUND_RESPONSES=0/1` | Opt in/out of stateful Responses `background: true` compatibility |
Expand Down
18 changes: 18 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Runtime configuration is resolved from unified settings, optional override files
},
"pluginConfig": {
"codexMode": true,
"codexRuntimeRotationProxy": false,
"liveAccountSync": true,
"sessionAffinity": true,
"proactiveRefreshGuardian": true,
Expand Down Expand Up @@ -63,6 +64,7 @@ These are safe for most operators and frequently used in day-to-day workflows.
| `CODEX_MULTI_AUTH_DIR` | Override root directory for plugin-managed runtime files |
| `CODEX_MULTI_AUTH_CONFIG_PATH` | Load configuration from alternate path |
| `CODEX_MODE=0/1` | Disable or enable Codex mode |
| `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=0/1` | Opt in to live Codex Responses routing through the localhost account-rotation proxy |
| `CODEX_TUI_V2=0/1` | Disable or enable TUI v2 |
| `CODEX_TUI_COLOR_PROFILE=truecolor|ansi256|ansi16` | Color profile selection |
| `CODEX_TUI_GLYPHS=ascii|unicode|auto` | Glyph mode selection |
Expand Down Expand Up @@ -99,6 +101,22 @@ Keep these enabled for most environments:

---

## Runtime Rotation Proxy

`codexRuntimeRotationProxy` is disabled by default. When enabled through settings, `codex auth rotation enable`, or `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=1`, the `codex` wrapper starts a localhost-only Responses proxy for forwarded official Codex sessions, including CLI request commands, `codex app-server`, and `codex app` launches through the wrapper. The wrapper writes a temporary shadow `CODEX_HOME/config.toml` that selects a custom provider named `codex-multi-auth-runtime-proxy`, launches the official Codex surface against that provider, and removes the shadow home after the owning process exits.

The proxy preserves request bodies and streaming responses, replaces outbound auth headers with the selected managed account, and rotates to another account before response bytes are streamed when it sees rate limits, server errors, network failures, or refresh failures. If every account is unavailable, the proxy returns a structured pool-exhaustion error that points to `codex auth rotation status`.

For `codex app` launches that go through the wrapper, the wrapper automatically starts a small internal helper so rotation can keep working if the desktop app launcher detaches. The helper stores only local runtime status, uses the same per-session proxy client key as the CLI path, and exits after an idle timeout.

`codex auth rotation enable` also binds the packaged desktop app to a persistent localhost router. This backs up the real Codex `config.toml`, writes the `codex-multi-auth-runtime-proxy` provider into the real Codex home, starts the router immediately, and installs a user login startup entry: a Startup `.cmd` on Windows or a LaunchAgent on macOS. The persistent provider is marked as not requiring OpenAI auth and uses a local app-bind client token, so the desktop runtime does not display the selected multi-auth account while codex-multi-auth status and quota views still read the router's last-account telemetry. `codex auth rotation disable` and `codex auth rotation unbind-app` stop that router, remove the startup entry, and restore the backed-up Codex config. The official app files are not patched.

Package install/update also self-heals this bind when runtime rotation was already enabled and a Codex desktop app is detected. Set `CODEX_MULTI_AUTH_APP_BIND_INSTALL=0` to skip install/update self-heal, or `CODEX_MULTI_AUTH_APP_BIND_INSTALL=1` to force it. Supported user-level launcher routing remains available for `.lnk` and managed wrapper app cases; set `CODEX_MULTI_AUTH_APP_LAUNCHER_INSTALL=0` before enabling rotation to skip that shortcut routing, or run `codex-multi-auth-app-launcher --remove` to restore backed-up Windows shortcuts or remove the managed macOS wrapper later.

Some Windows installs expose Codex only as a packaged `shell:AppsFolder` app entry. Those entries cannot be retargeted like `.lnk` files, so the persistent app bind is the supported path for making the pinned packaged app use rotation automatically.

---

## Shipped Templates

The shipped config templates expose first-class GPT-5.5 model aliases:
Expand Down
2 changes: 2 additions & 0 deletions docs/development/CONFIG_FIELDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Used only for host plugin mode through the host runtime config file.
| Key | Default |
| --- | --- |
| `codexMode` | `true` |
| `codexRuntimeRotationProxy` | `false` |
| `codexTuiV2` | `true` |
| `codexTuiColorProfile` | `truecolor` |
| `codexTuiGlyphMode` | `ascii` |
Expand Down Expand Up @@ -200,6 +201,7 @@ Upgrade note:
| `CODEX_MULTI_AUTH_DIR` | Custom root for settings/accounts/cache/logs |
| `CODEX_MULTI_AUTH_CONFIG_PATH` | Alternate config file input |
| `CODEX_MODE` | Toggle Codex mode |
| `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY` | Toggle opt-in localhost Responses proxy for forwarded Codex sessions (`1`/`true` to enable, `0`/`false` to disable) |
| `CODEX_TUI_V2` | Toggle TUI v2 |
| `CODEX_TUI_COLOR_PROFILE` | TUI color profile |
| `CODEX_TUI_GLYPHS` | TUI glyph mode |
Expand Down
1 change: 1 addition & 0 deletions docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ User-facing capability map for `codex-multi-auth`.
| Readiness and risk forecast | Suggests the best next account | `codex auth forecast` |
| Live quota probe mode | Uses live headers for stronger decisions | `codex auth forecast --live` |
| JSON report output | Lets you inspect account state in automation or support workflows | `codex auth report --live --json` |
| Runtime rotation proxy (opt-in) | Lets forwarded official Codex CLI/app sessions rotate managed accounts between Responses requests without restarting the session. Disabled by default; enable per install. | `codex auth rotation enable` |

---

Expand Down
34 changes: 34 additions & 0 deletions docs/reference/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ Compatibility aliases are supported:
| `codex auth features` | Print implemented feature summary |
| `codex auth report` | Generate full health report |
| `codex auth why-selected [--now|--last]` | Explain which account the selector picks now or via the last persisted runtime snapshot |
| `codex auth rotation enable\|disable\|status\|bind-app\|unbind-app` | Manage the opt-in runtime Responses proxy for live Codex account rotation |

---

Expand Down Expand Up @@ -150,6 +151,39 @@ The `runtimeSnapshot` field is present only with `--last`. `selected` is

---

## `codex auth rotation`

Manages the opt-in runtime Responses proxy used by forwarded official Codex sessions. This is separate from normal `codex auth switch`: the proxy can rotate managed accounts between backend Responses requests while a Codex session stays open.

Usage:

```bash
codex auth rotation enable
codex auth rotation disable
codex auth rotation status
codex auth rotation bind-app
codex auth rotation unbind-app
```

Behavior:

- `enable` persists `codexRuntimeRotationProxy=true`, binds the packaged desktop app to the same persistent localhost router, and routes supported user-level app shortcuts when possible.
- `disable` persists `codexRuntimeRotationProxy=false` and removes the persistent packaged-app bind.
- `status` prints the effective setting, environment override state, automatic Codex app helper state, persistent Codex app bind state, account count, current account, disabled accounts, cooldowns, and rate-limit waits.
- `bind-app` repairs or installs the persistent packaged-app bind without changing the stored rotation setting.
- `unbind-app` removes the persistent packaged-app bind and restores the backed-up Codex config.
- `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=1` enables the proxy for the current process without changing settings.

When enabled, the wrapper creates a temporary shadow `CODEX_HOME/config.toml` with a custom provider named `codex-multi-auth-runtime-proxy`, starts a `127.0.0.1` proxy on a random port, and forwards official Codex Responses traffic through that provider. This applies to CLI request commands plus `codex app-server` and `codex app` when they are launched through the wrapper. Existing behavior is unchanged while the setting and env override are off.

Packaged desktop app support uses a reversible bind instead of patching app files. It backs up the real Codex `config.toml`, writes the same custom provider to the real Codex home, starts a localhost-only router, and installs a user login startup entry: a Startup `.cmd` on Windows or a LaunchAgent on macOS. The provider uses a local app-bind client token and `requires_openai_auth=false`, which keeps the selected multi-auth account out of the runtime composer while preserving router last-account telemetry for codex-multi-auth status and quota views. Package install/update runs the same bind only when runtime rotation was already enabled and a Codex desktop app is detected; set `CODEX_MULTI_AUTH_APP_BIND_INSTALL=0` to skip that self-heal or `CODEX_MULTI_AUTH_APP_BIND_INSTALL=1` to force it.

The app launcher routing helper is also available directly as `codex-multi-auth-app-launcher`. On Windows, it retargets existing user-level `Codex` shortcuts and taskbar pins to the wrapper while backing up their original target for restore. On macOS, it creates or removes a user-level `Codex Multi Auth.app` wrapper because Dock entries cannot safely launch a shell command directly. It does not patch the official app files. Use `codex-multi-auth-app-launcher --remove` to restore backed-up Windows shortcuts or remove the managed macOS wrapper.

If Windows exposes Codex only as a packaged `shell:AppsFolder` entry, shortcut routing may still report that there is no retargetable `.lnk`. The persistent app bind is the path that makes those packaged entries use rotation when the official app is opened directly.

---

## `codex auth verify`

Supersedes `codex auth verify-flagged` as a single entry point for
Expand Down
2 changes: 2 additions & 0 deletions docs/reference/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ Common operator overrides:
- `CODEX_MULTI_AUTH_DIR`
- `CODEX_MULTI_AUTH_CONFIG_PATH`
- `CODEX_MODE`
- `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY`
- `CODEX_MULTI_AUTH_APP_BIND_INSTALL`
- `CODEX_TUI_V2`
- `CODEX_TUI_COLOR_PROFILE`
- `CODEX_TUI_GLYPHS`
Expand Down
8 changes: 8 additions & 0 deletions docs/releases/v1.3.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ This patch release finalizes the GPT-5.5 runtime rollout work for `codex-multi-a
- verified native `gpt-5.5` behavior on official Codex `0.124.0`
- preserved deterministic fallback behavior for older official Codex runtimes and non-entitled or quota-limited accounts

### Runtime Rotation Proxy

- added opt-in `codexRuntimeRotationProxy` and `CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY=1`
- added `codex auth rotation enable|disable|status`
- forwarded official Codex sessions can use a temporary shadow `CODEX_HOME` and localhost Responses proxy to rotate managed accounts between backend requests
- `codex app-server` and wrapper-launched `codex app` now use the same runtime rotation path automatically when rotation is enabled
- rotation enable routes existing Windows user-level `Codex` shortcuts and taskbar pins through the wrapper-backed `codex app` path, and creates a managed macOS wrapper for the same path, without patching official Codex app files

Comment thread
coderabbitai[bot] marked this conversation as resolved.
## Validation

- `npm run build`
Expand Down
77 changes: 76 additions & 1 deletion lib/codex-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import {
import { runForecastCommand } from "./codex-manager/commands/forecast.js";
import { runInitConfigCommand } from "./codex-manager/commands/init-config.js";
import { runReportCommand } from "./codex-manager/commands/report.js";
import { runRotationCommand } from "./codex-manager/commands/rotation.js";
import {
runFeaturesCommand,
runStatusCommand,
Expand All @@ -83,7 +84,17 @@ import {
configureUnifiedSettings,
resolveMenuLayoutMode,
} from "./codex-manager/settings-hub.js";
import { getPluginConfigExplainReport } from "./config.js";
import {
getCodexRuntimeRotationProxy,
getPluginConfigExplainReport,
loadPluginConfig,
savePluginConfig,
} from "./config.js";
import {
bindCodexAppRuntimeRotation,
getAppBindStatus,
unbindCodexAppRuntimeRotation,
} from "./runtime/app-bind.js";
import { ACCOUNT_LIMITS } from "./constants.js";
import {
type DashboardAccountSortMode,
Expand Down Expand Up @@ -1193,6 +1204,55 @@ function toExistingAccountInfo(
}));
}

function activeAccountMatchesCodexCliState(
account: AccountMetadataV3,
state: Awaited<ReturnType<typeof loadCodexCliState>>,
): boolean {
if (!state) return true;
const accountId = account.accountId?.trim();
const activeAccountId = state.activeAccountId?.trim();
if (accountId && activeAccountId) {
return accountId === activeAccountId;
}

const email = sanitizeEmail(account.email);
const activeEmail = sanitizeEmail(state.activeEmail);
if (email && activeEmail) {
return email === activeEmail;
}

return false;
}

async function syncCodexCliActiveSelectionIfDrifted(
storage: AccountStorageV3,
): Promise<boolean> {
const activeIndex = resolveActiveIndex(storage, "codex");
if (activeIndex < 0 || activeIndex >= storage.accounts.length) {
return false;
}
const account = storage.accounts[activeIndex];
if (!account) {
return false;
}

try {
const cliState = await loadCodexCliState({ forceRefresh: true });
if (!cliState || activeAccountMatchesCodexCliState(account, cliState)) {
return false;
}
return setCodexCliActiveSelection({
accountId: account.accountId,
email: account.email,
accessToken: account.accessToken,
refreshToken: account.refreshToken,
expiresAt: account.expiresAt,
});
} catch {
return false;
}
}

function resolveAccountSelection(
tokens: TokenSuccess,
): TokenSuccessWithAccount {
Expand Down Expand Up @@ -2726,6 +2786,7 @@ async function runAuthLogin(args: string[]): Promise<number> {
}
}
const flaggedStorage = await loadFlaggedAccounts();
await syncCodexCliActiveSelectionIfDrifted(currentStorage);

const menuResult = await promptLoginMode(
toExistingAccountInfo(currentStorage, quotaCache, displaySettings),
Expand Down Expand Up @@ -3378,6 +3439,20 @@ export async function runCodexMultiAuthCli(rawArgs: string[]): Promise<number> {
loadPersistedRuntimeObservabilitySnapshot,
});
}
if (command === "rotation") {
return runRotationCommand(rest, {
loadPluginConfig,
savePluginConfig,
getCodexRuntimeRotationProxy,
setStoragePath,
getStoragePath,
loadAccounts,
resolveActiveIndex,
bindCodexApp: bindCodexAppRuntimeRotation,
unbindCodexApp: unbindCodexAppRuntimeRotation,
getCodexAppBindStatus: getAppBindStatus,
});
}
if (command === "why-selected") {
return runWhySelectedCommand(rest, {
parseWhySelectedArgs,
Expand Down
Loading