Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,9 +308,9 @@ codex auth doctor --json

## Release Notes

- Current stable: [docs/releases/v1.2.2.md](docs/releases/v1.2.2.md)
- Previous stable: [docs/releases/v1.2.1.md](docs/releases/v1.2.1.md)
- Earlier stable: [docs/releases/v1.2.0.md](docs/releases/v1.2.0.md)
- Current stable: [docs/releases/v1.2.3.md](docs/releases/v1.2.3.md)
- Previous stable: [docs/releases/v1.2.2.md](docs/releases/v1.2.2.md)
- Earlier stable: [docs/releases/v1.2.1.md](docs/releases/v1.2.1.md)
- Archived prerelease: [docs/releases/v0.1.0-beta.0.md](docs/releases/v0.1.0-beta.0.md)

## License
Expand Down
9 changes: 5 additions & 4 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ Public documentation for `codex-multi-auth`.
| [configuration.md](configuration.md) | Stable defaults, precedence, and environment overrides |
| [architecture.md](architecture.md) | Public system overview of the wrapper, storage, and optional plugin runtime |
| [privacy.md](privacy.md) | Data handling and local storage behavior |
| [releases/v1.2.2.md](releases/v1.2.2.md) | Stable release notes |
| [releases/v1.2.1.md](releases/v1.2.1.md) | Previous stable release notes |
| [releases/v1.2.0.md](releases/v1.2.0.md) | Earlier stable release notes |
| [releases/v1.2.3.md](releases/v1.2.3.md) | Stable release notes |
| [releases/v1.2.2.md](releases/v1.2.2.md) | Previous stable release notes |
| [releases/v1.2.1.md](releases/v1.2.1.md) | Earlier stable release notes |
| [releases/v1.2.0.md](releases/v1.2.0.md) | Archived stable release notes |
| [releases/v0.1.7.md](releases/v0.1.7.md) | Archived stable release notes |
| [releases/v0.1.6.md](releases/v0.1.6.md) | Archived stable release notes |
| [releases/v0.1.5.md](releases/v0.1.5.md) | Archived stable release notes |
Expand All @@ -51,7 +52,7 @@ Public documentation for `codex-multi-auth`.
| [reference/storage-paths.md](reference/storage-paths.md) | Canonical and compatibility storage paths |
| [reference/public-api.md](reference/public-api.md) | Public API stability and semver contract |
| [reference/error-contracts.md](reference/error-contracts.md) | CLI, JSON, and helper error semantics |
| [releases/v1.2.2.md](releases/v1.2.2.md) | Current stable release notes |
| [releases/v1.2.3.md](releases/v1.2.3.md) | Current stable release notes |
| [releases/v0.1.0-beta.0.md](releases/v0.1.0-beta.0.md) | Archived prerelease reference |
| [Daily Use release notes](#daily-use) | Stable, previous, and archived release notes |
| [releases/legacy-pre-0.1-history.md](releases/legacy-pre-0.1-history.md) | Archived pre-0.1 changelog history |
Expand Down
9 changes: 9 additions & 0 deletions docs/reference/storage-paths.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Override root:
| File | Default path |
| --- | --- |
| Unified settings | `~/.codex/multi-auth/settings.json` |
| Unified settings backup | `~/.codex/multi-auth/settings.json.bak` |
| Accounts | `~/.codex/multi-auth/openai-codex-accounts.json` |
| Accounts backup | `~/.codex/multi-auth/openai-codex-accounts.json.bak` |
| Accounts WAL | `~/.codex/multi-auth/openai-codex-accounts.json.wal` |
Expand Down Expand Up @@ -48,6 +49,14 @@ Compatibility note:
Backup metadata:

- `getBackupMetadata()` reports deterministic snapshot lists for the canonical account pool (primary, WAL, `.bak`, `.bak.1`, `.bak.2`, and discovered manual backups) and flagged-account state (primary, `.bak`, `.bak.1`, `.bak.2`, and discovered manual backups). Cache-like artifacts and `.reset-intent` markers are excluded from recovery candidates.
- `settings.json.bak` stores the last valid unified settings snapshot before each write and is used as a recovery fallback when `settings.json` is unreadable.
- Flagged-account backup recovery is suppressed whenever the flagged reset marker is still present, so partial clears cannot revive previously cleared flagged entries.

Upgrade note:

- Restore workflows now distinguish between unreadable state and intentionally cleared state. `settings.json.bak` is only used when `settings.json` exists but cannot be read, while flagged-account backups stay suppressed whenever the reset marker survives a partial clear.
- Operators validating a restore or clear flow should use `codex auth verify-flagged`, `codex auth fix --dry-run`, and `codex auth doctor --fix` to confirm what will be recovered, what stays cleared, and whether manual repair is still needed.
- Maintainers validating the on-disk upgrade behavior can run `npm run build` plus `npm test -- --run test/unified-settings.test.ts test/storage-recovery-paths.test.ts test/storage-flagged.test.ts` before shipping backup or restore changes.

---

Expand Down
53 changes: 53 additions & 0 deletions docs/releases/v1.2.3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Release v1.2.3

Release line: `stable`

This release supersedes the open `main`-target PR wave with one rebuilt, validated integration branch.

## Scope

- Current package version in `package.json` is `1.2.3`.
- Canonical command family remains `codex auth ...`.
- Canonical package name remains `codex-multi-auth`.
- The release branch rebuild starts from `origin/main` commit `cbce5f5c3c5588c08a388d600306366ccb95a6a7`.

## What Changed

- remediated the `audit:ci` dependency findings and refreshed the lockfile on current `main`
- fixed ready-first account ordering, including the menu auto-refresh race that could re-skip a later refresh
- hardened the Codex wrapper compatibility path so unsupported reasoning-effort config is rewritten correctly, staged auth state syncs back before cleanup, and cleanup-failure tests do not rely on source rewriting
- fixed usage-limit cooldown persistence across account state, fallback 429 handling, and quota scheduling without dropping secondary quota state
- carried forward the config validation, unified-settings backup recovery, and flagged-backup recovery hardening from the older config/storage PR lane

## Included PR Lanes

- `#351` `chore: remediate audit-ci dependency findings`
- `#352` `fix ready-first account ordering regressions`
- `#353` `fix codex wrapper compatibility handling`
- `#354` `fix usage-limit cooldown persistence`
- `#344` `fix config validation and flagged backup recovery`

## Superseded PR Stack

- `#344` `fix config validation and flagged backup recovery`
- `#351` `chore: remediate audit-ci dependency findings`
- `#352` `fix ready-first account ordering regressions`
- `#353` `fix codex wrapper compatibility handling`
- `#354` `fix usage-limit cooldown persistence`

## Validation

- `npm run lint`
- `npm run typecheck`
- `npm test -- --pool=threads --maxWorkers=1`
- `npm run build`
- `npm run clean:repo:check`
- `npm run audit:ci`
- Full suite passed: `222/222` files, `3292/3292` tests

## Related

- [../getting-started.md](../getting-started.md)
- [../upgrade.md](../upgrade.md)
- [../reference/commands.md](../reference/commands.md)
- [../reference/public-api.md](../reference/public-api.md)
50 changes: 36 additions & 14 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1858,20 +1858,26 @@ export const OpenAIOAuthPlugin: Plugin = async ({ client }: PluginInput) => {
break;
}

if (rateLimit) {
if (response.status === 429) {
runtimeMetrics.rateLimitedResponses++;
const retryAfterMs =
rateLimit?.retryAfterMs ?? 60_000;
const { attempt, delayMs } = getRateLimitBackoff(
account.index,
quotaKey,
rateLimit.retryAfterMs,
retryAfterMs,
);
const cooldownMs = Math.max(
delayMs,
retryAfterMs,
);
preemptiveQuotaScheduler.markRateLimited(
quotaScheduleKey,
delayMs,
cooldownMs,
);
const waitLabel = formatWaitTime(delayMs);
const waitLabel = formatWaitTime(cooldownMs);

if (delayMs <= RATE_LIMIT_SHORT_RETRY_THRESHOLD_MS) {
if (cooldownMs <= RATE_LIMIT_SHORT_RETRY_THRESHOLD_MS) {
if (
accountManager.shouldShowAccountToast(
account.index,
Expand All @@ -1887,16 +1893,16 @@ export const OpenAIOAuthPlugin: Plugin = async ({ client }: PluginInput) => {
}

await sleep(
addJitter(Math.max(MIN_BACKOFF_MS, delayMs), 0.2),
addJitter(Math.max(MIN_BACKOFF_MS, cooldownMs), 0.2),
);
continue;
}

accountManager.markRateLimitedWithReason(
account,
delayMs,
cooldownMs,
modelFamily,
parseRateLimitReason(rateLimit.code),
parseRateLimitReason(rateLimit?.code),
model,
);
accountManager.recordRateLimit(
Expand Down Expand Up @@ -2142,19 +2148,34 @@ export const OpenAIOAuthPlugin: Plugin = async ({ client }: PluginInput) => {
);
}
if (!fallbackResponse.ok) {
const { response: handledFallbackResponse, rateLimit: fallbackRateLimit } =
await handleErrorResponse(fallbackResponse);
try {
await fallbackResponse.body?.cancel();
} catch {
// Best effort cleanup before trying next fallback account.
// Best-effort only; the error body has already been read.
}
if (fallbackResponse.status === 429) {
if (handledFallbackResponse.status === 429) {
const retryAfterMs =
parseRetryAfterHintMs(
fallbackResponse.headers,
) ?? 60_000;
fallbackRateLimit?.retryAfterMs ?? 60_000;
const fallbackQuotaKey =
model ? `${modelFamily}:${model}` : modelFamily;
const { delayMs } = getRateLimitBackoff(
fallbackAccount.index,
fallbackQuotaKey,
retryAfterMs,
);
const cooldownMs = Math.max(
delayMs,
retryAfterMs,
);
preemptiveQuotaScheduler.markRateLimited(
`${fallbackEntitlementAccountKey}:${model ?? modelFamily}`,
cooldownMs,
);
accountManager.markRateLimitedWithReason(
fallbackAccount,
retryAfterMs,
cooldownMs,
modelFamily,
"quota",
model,
Expand All @@ -2164,6 +2185,7 @@ export const OpenAIOAuthPlugin: Plugin = async ({ client }: PluginInput) => {
modelFamily,
model,
);
accountManager.saveToDiskDebounced();
} else {
accountManager.recordFailure(
fallbackAccount,
Expand Down
6 changes: 4 additions & 2 deletions lib/accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -783,15 +783,17 @@ export class AccountManager {

const baseKey = getQuotaKey(family);
if (!model || reason === "quota" || reason === "unknown") {
account.rateLimitResetTimes[baseKey] = resetAt;
const currentResetAt = account.rateLimitResetTimes[baseKey] ?? 0;
account.rateLimitResetTimes[baseKey] = Math.max(currentResetAt, resetAt);
}

if (
model &&
(reason === "tokens" || reason === "concurrent" || reason === "unknown")
) {
const modelKey = getQuotaKey(family, model);
account.rateLimitResetTimes[modelKey] = resetAt;
const currentResetAt = account.rateLimitResetTimes[modelKey] ?? 0;
account.rateLimitResetTimes[modelKey] = Math.max(currentResetAt, resetAt);
}

account.lastRateLimitReason = reason;
Expand Down
Loading