Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
af7f137
fix(auth): define reset and delete safety flows
ndycode Mar 15, 2026
f48ee06
feat(auth): add backup restore manager
ndycode Mar 15, 2026
12074e2
feat(auth): prompt for recovery on startup
ndycode Mar 15, 2026
8212831
test(storage): isolate empty export path
ndycode Mar 15, 2026
eeecf6e
feat(sync): add sync center and status surface
ndycode Mar 15, 2026
ccb06f6
fix(auth): clean startup recovery prompt branch
ndycode Mar 16, 2026
2ee71fb
fix(sync): harden sync center preview and apply
ndycode Mar 16, 2026
1eeec0b
Block non-interactive backup restore mode
ndycode Mar 16, 2026
9ec55db
Fix backup import accounting regressions
ndycode Mar 16, 2026
326d5f1
fix(auth): harden restore backup fallback and file reads
ndycode Mar 16, 2026
11c503c
fix(sync): close backup restore review gaps
ndycode Mar 16, 2026
e0e7680
fix(sync): preserve sync run timestamps
ndycode Mar 16, 2026
19b50d3
fix(sync): resolve review blockers
ndycode Mar 17, 2026
922c5de
fix(sync): close remaining review blockers
ndycode Mar 17, 2026
ce4fbf3
test(sync): cover retryable timestamp and scan errors
ndycode Mar 17, 2026
8ba80e7
Retry sync timestamp reads on EIO
ndycode Mar 17, 2026
dbe3c1c
Harden recovery backup reads
ndycode Mar 17, 2026
1a7ae95
test(sync): cover ENXIO and UNC sync-center paths
ndycode Mar 17, 2026
ea74af4
fix(restore): reuse pre-assessed backup restore
ndycode Mar 17, 2026
ef68e86
fix(sync): ignore storage-noise watch artifacts
ndycode Mar 17, 2026
f811d3d
Redact sync center filesystem errors
ndycode Mar 17, 2026
5e174c8
Redact stored sync run errors
ndycode Mar 17, 2026
7215e01
fix(sync): harden restore manager follow-ups
ndycode Mar 17, 2026
56e715a
fix(sync): stop startup recovery reprompt loop
ndycode Mar 17, 2026
2a8d6f2
fix(sync): align startup recovery prompt counts
ndycode Mar 17, 2026
a29190a
fix(sync): normalize UNC source path labels
ndycode Mar 18, 2026
e7f4db1
fix(sync): reassess named restore safety
ndycode Mar 18, 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
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Codex CLI-first multi-account OAuth manager for the official `@openai/codex` CLI
### Option A: Standard install

```bash
npm i -g @openai/codex
npm i -g codex-multi-auth
```

Expand All @@ -74,16 +75,18 @@ codex auth status

### Step-by-step

1. Install global package:
1. Install global packages:
- `npm i -g @openai/codex`
- `npm i -g codex-multi-auth`
2. Run first login flow with `codex auth login`
3. Validate state with `codex auth status` and `codex auth check`
3. Validate state with `codex auth list` and `codex auth check`
4. Confirm routing with `codex auth forecast --live`

### Verification

```bash
codex auth status
codex auth list
codex auth check
```

Expand All @@ -95,7 +98,7 @@ codex auth check

```bash
codex auth login
codex auth status
codex auth list
codex auth check
codex auth forecast --live
```
Expand Down
2 changes: 2 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ Expected flow:
4. Return to the terminal when the browser step completes.
5. Confirm the account appears in the saved account list.

If interactive `codex auth login` starts with zero saved accounts and recoverable named backups in your `backups/` directory, the login flow will prompt you to restore before opening OAuth. Confirm to launch the existing restore manager; skip to proceed with a fresh login. The prompt is suppressed in non-interactive/fallback flows and after same-session `fresh` or `reset` actions.

Verify the new account:

```bash
Expand Down
2 changes: 2 additions & 0 deletions docs/reference/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Compatibility aliases are supported:
| `codex auth switch <index>` | Set active account by index |
| `codex auth check` | Run quick account health check |
| `codex auth features` | Print implemented feature summary |
| `codex auth restore-backup` | Open the backup restore picker directly |

---

Expand Down Expand Up @@ -111,6 +112,7 @@ codex auth report --live --json
Repair and recovery:

```bash
codex auth restore-backup
codex auth fix --dry-run
codex auth fix --live --model gpt-5-codex
codex auth doctor --fix
Expand Down
6 changes: 6 additions & 0 deletions docs/reference/public-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ Compatibility policy for Tier B:
- Existing exported symbols must not be removed in this release line.
- Deprecated usage may be documented, but hard removals require a major version transition plan.

Current additive compatibility note:

- `importAccounts()` now returns `{ imported, total, skipped, changed }` at runtime.
- The exported `ImportAccountsResult` type keeps `changed` optional so older callers modeling the legacy shape remain source-compatible.
- New callers should read `changed` to distinguish duplicate-only no-ops from metadata-refresh writes.

### Tier C: Internal APIs

Internal APIs are any non-exported internals and implementation details not covered by Tier A or Tier B.
Expand Down
14 changes: 14 additions & 0 deletions docs/reference/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,20 @@ Controls display style:
- `uiAccentColor`
- `menuFocusStyle`

## Sync Center

The settings hub includes a preview-first sync center for Codex CLI account sync.
See [upgrade notes](../upgrade.md) for sync workflow changes.

Before applying sync, it shows:

- target path
- current source path when available
- last sync result for this session
- preview summary (adds, updates, destination-only preserved accounts)
- destination-only preservation behavior
- backup and rollback context (`.bak`, `.bak.1`, `.bak.2`, `.wal`)

---

## Experimental
Expand Down
14 changes: 14 additions & 0 deletions docs/reference/storage-paths.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Override root:
| --- | --- |
| Unified settings | `~/.codex/multi-auth/settings.json` |
| Accounts | `~/.codex/multi-auth/openai-codex-accounts.json` |
| Named backups | `~/.codex/multi-auth/backups/<name>.json` |
| Accounts backup | `~/.codex/multi-auth/openai-codex-accounts.json.bak` |
| Accounts WAL | `~/.codex/multi-auth/openai-codex-accounts.json.wal` |
| Flagged accounts | `~/.codex/multi-auth/openai-codex-flagged-accounts.json` |
Expand Down Expand Up @@ -56,6 +57,7 @@ Backup metadata:
When project-scoped behavior is enabled:

- `~/.codex/multi-auth/projects/<project-key>/openai-codex-accounts.json`
- `~/.codex/multi-auth/projects/<project-key>/backups/<name>.json`

`<project-key>` is derived as:

Expand Down Expand Up @@ -100,6 +102,17 @@ Rules:
- `.rotate.`, `.tmp`, and `.wal` names are rejected
- existing files are not overwritten unless a lower-level force path is used explicitly

Restore workflow:

1. Run `codex auth login`.
2. Open the `Recovery` section.
3. Choose `Restore From Backup`.
4. Pick a backup and confirm the merge summary before import.

Direct entrypoint:

- Run `codex auth restore-backup` to open the same picker without entering the full login dashboard first.

---

## oc-chatgpt Target Paths
Expand All @@ -115,6 +128,7 @@ Experimental sync targets the companion `oc-chatgpt-multi-auth` storage layout:
## Verification Commands

```bash
codex auth login
codex auth status
codex auth list
```
Expand Down
46 changes: 38 additions & 8 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ If the account pool is still not usable:
codex auth login
```

If `codex auth login` starts with no saved accounts and recoverable named backups are present, you will be prompted to restore before OAuth. This prompt only appears in interactive terminals and is skipped after same-session fresh/reset flows.

---

## Verify Install And Routing
Expand Down Expand Up @@ -89,24 +91,52 @@ codex auth doctor --json

---

## Soft Reset
## Reset Options

- Delete a single saved account: `codex auth login` → pick account → **Delete Account**
- Delete saved accounts: `codex auth login` → Danger Zone → **Delete Saved Accounts**
- Reset local state: `codex auth login` → Danger Zone → **Reset Local State**

Exact effects:

| Action | Saved accounts | Flagged/problem accounts | Settings | Codex CLI sync state | Quota cache |
| --- | --- | --- | --- | --- | --- |
| Delete Account | Delete the selected saved account | Delete the matching flagged/problem entry for that refresh token | Keep | Keep | Keep |
| Delete Saved Accounts | Delete all saved accounts | Keep | Keep | Keep | Keep |
| Reset Local State | Delete all saved accounts | Delete all flagged/problem accounts | Keep | Keep | Clear |

To perform the same actions manually:

PowerShell:
Delete saved accounts only:

```powershell
Remove-Item "$HOME\.codex\multi-auth\openai-codex-accounts.json" -Force -ErrorAction SilentlyContinue
Remove-Item "$HOME\.codex\multi-auth\openai-codex-flagged-accounts.json" -Force -ErrorAction SilentlyContinue
Remove-Item "$HOME\.codex\multi-auth\settings.json" -Force -ErrorAction SilentlyContinue
codex auth login
Remove-Item "$HOME\.codex\multi-auth\openai-codex-accounts.json.wal" -Force -ErrorAction SilentlyContinue
Remove-Item "$HOME\.codex\multi-auth\openai-codex-accounts.json.bak*" -Force -ErrorAction SilentlyContinue
```

Bash:
```bash
rm -f ~/.codex/multi-auth/openai-codex-accounts.json
rm -f ~/.codex/multi-auth/openai-codex-accounts.json.wal
rm -f ~/.codex/multi-auth/openai-codex-accounts.json.bak*
```

Reset local state (also clears flagged/problem accounts and quota cache; preserves settings and Codex CLI sync state):

```powershell
Remove-Item "$HOME\.codex\multi-auth\openai-codex-accounts.json" -Force -ErrorAction SilentlyContinue
Remove-Item "$HOME\.codex\multi-auth\openai-codex-accounts.json.wal" -Force -ErrorAction SilentlyContinue
Remove-Item "$HOME\.codex\multi-auth\openai-codex-accounts.json.bak*" -Force -ErrorAction SilentlyContinue
Remove-Item "$HOME\.codex\multi-auth\openai-codex-flagged-accounts.json" -Force -ErrorAction SilentlyContinue
Remove-Item "$HOME\.codex\multi-auth\quota-cache.json" -Force -ErrorAction SilentlyContinue
```

```bash
rm -f ~/.codex/multi-auth/openai-codex-accounts.json
rm -f ~/.codex/multi-auth/openai-codex-accounts.json.wal
rm -f ~/.codex/multi-auth/openai-codex-accounts.json.bak*
rm -f ~/.codex/multi-auth/openai-codex-flagged-accounts.json
rm -f ~/.codex/multi-auth/settings.json
codex auth login
rm -f ~/.codex/multi-auth/quota-cache.json
```

---
Expand Down
14 changes: 14 additions & 0 deletions docs/upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,20 @@ After source selection, environment variables still override individual setting
For day-to-day operator use, prefer stable overrides documented in [configuration.md](configuration.md).
For maintainer/debug flows, see advanced/internal controls in [development/CONFIG_FIELDS.md](development/CONFIG_FIELDS.md).

### Startup Recovery Prompt

Interactive `codex auth login` now offers named-backup recovery before OAuth only when the session starts with zero saved accounts and at least one recoverable named backup.

The prompt is intentionally skipped in fallback/non-interactive login paths and after same-session `fresh` or `reset` actions so an intentional wipe does not immediately re-offer restore state.

### Sync Center

The settings hub now includes a preview-first Sync Center for Codex CLI reconciliation.

- preview shows source and target paths, last session sync status, and whether destination-only accounts are preserved
- apply is blocked until the preview status is ready
- rollback context surfaces backup paths such as `.bak`, `.bak.1`, `.bak.2`, and `.wal`

---

## Legacy Compatibility
Expand Down
56 changes: 39 additions & 17 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ import {
} from "./lib/logger.js";
import { checkAndNotify } from "./lib/auto-update-checker.js";
import { handleContextOverflow } from "./lib/context-overflow.js";
import {
DESTRUCTIVE_ACTION_COPY,
deleteAccountAtIndex,
deleteSavedAccounts,
resetLocalState,
} from "./lib/destructive-actions.js";
import {
AccountManager,
getAccountIdCandidates,
Expand All @@ -122,13 +128,11 @@ import {
loadAccounts,
saveAccounts,
withAccountStorageTransaction,
clearAccounts,
setStoragePath,
exportAccounts,
importAccounts,
loadFlaggedAccounts,
saveFlaggedAccounts,
clearFlaggedAccounts,
findMatchingAccountIndex,
StorageError,
formatStorageErrorHint,
Expand Down Expand Up @@ -3101,19 +3105,18 @@ while (attempted.size < Math.max(1, accountCount)) {

if (menuResult.mode === "manage") {
if (typeof menuResult.deleteAccountIndex === "number") {
const target = workingStorage.accounts[menuResult.deleteAccountIndex];
if (target) {
workingStorage.accounts.splice(menuResult.deleteAccountIndex, 1);
clampActiveIndices(workingStorage);
await saveAccounts(workingStorage);
await saveFlaggedAccounts({
version: 1,
accounts: flaggedStorage.accounts.filter(
(flagged) => flagged.refreshToken !== target.refreshToken,
),
});
const deleted = await deleteAccountAtIndex({
storage: workingStorage,
index: menuResult.deleteAccountIndex,
});
if (deleted) {
invalidateAccountManagerCache();
console.log(`\nDeleted ${target.email ?? `Account ${menuResult.deleteAccountIndex + 1}`}.\n`);
const label = `Account ${menuResult.deleteAccountIndex + 1}`;
const flaggedNote =
deleted.removedFlaggedCount > 0
? ` Removed ${deleted.removedFlaggedCount} matching problem account${deleted.removedFlaggedCount === 1 ? "" : "s"}.`
: "";
console.log(`\nDeleted ${label}.${flaggedNote}\n`);
}
continue;
}
Expand Down Expand Up @@ -3143,16 +3146,35 @@ while (attempted.size < Math.max(1, accountCount)) {
if (menuResult.mode === "fresh") {
startFresh = true;
if (menuResult.deleteAll) {
await clearAccounts();
await clearFlaggedAccounts();
const result = await deleteSavedAccounts();
invalidateAccountManagerCache();
console.log(
"\nCleared saved accounts from active storage. Recovery snapshots remain available. Starting fresh.\n",
`\n${
result.accountsCleared
? DESTRUCTIVE_ACTION_COPY.deleteSavedAccounts.completed
: "Delete saved accounts completed with warnings. Some saved account artifacts could not be removed; see logs."
}\n`,
);
}
break;
}

if (menuResult.mode === "reset") {
startFresh = true;
const result = await resetLocalState();
invalidateAccountManagerCache();
console.log(
`\n${
result.accountsCleared &&
result.flaggedCleared &&
result.quotaCacheCleared
? DESTRUCTIVE_ACTION_COPY.resetLocalState.completed
: "Reset local state completed with warnings. Some local artifacts could not be removed; see logs."
}\n`,
);
break;
}

startFresh = false;
break;
}
Expand Down
8 changes: 7 additions & 1 deletion lib/accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ import {
loadCodexCliState,
type CodexCliTokenCacheEntry,
} from "./codex-cli/state.js";
import { syncAccountStorageFromCodexCli } from "./codex-cli/sync.js";
import {
commitCodexCliSyncRunFailure,
commitPendingCodexCliSyncRun,
syncAccountStorageFromCodexCli,
} from "./codex-cli/sync.js";
import { setCodexCliActiveSelection } from "./codex-cli/writer.js";

export {
Expand Down Expand Up @@ -116,7 +120,9 @@ export class AccountManager {
if (synced.changed && sourceOfTruthStorage) {
try {
await saveAccounts(sourceOfTruthStorage);
commitPendingCodexCliSyncRun(synced.pendingRun);
} catch (error) {
commitCodexCliSyncRunFailure(synced.pendingRun, error);
log.debug("Failed to persist Codex CLI source-of-truth sync", {
error: String(error),
});
Expand Down
Loading