Skip to content

Commit e782e09

Browse files
authored
Merge pull request #356 from ndycode/release/post-merge-review-fixes-1.2.4
release: patch post-merge review fixes for v1.2.4
2 parents ab096bf + 56d76c9 commit e782e09

18 files changed

Lines changed: 1032 additions & 55 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ codex auth doctor --json
308308

309309
## Release Notes
310310

311-
- Current stable: [docs/releases/v1.2.3.md](docs/releases/v1.2.3.md)
311+
- Current stable: [docs/releases/v1.2.4.md](docs/releases/v1.2.4.md)
312312
- Previous stable: [docs/releases/v1.2.2.md](docs/releases/v1.2.2.md)
313313
- Earlier stable: [docs/releases/v1.2.1.md](docs/releases/v1.2.1.md)
314314
- Archived prerelease: [docs/releases/v0.1.0-beta.0.md](docs/releases/v0.1.0-beta.0.md)

docs/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Public documentation for `codex-multi-auth`.
2323
| [configuration.md](configuration.md) | Stable defaults, precedence, and environment overrides |
2424
| [architecture.md](architecture.md) | Public system overview of the wrapper, storage, and optional plugin runtime |
2525
| [privacy.md](privacy.md) | Data handling and local storage behavior |
26-
| [releases/v1.2.3.md](releases/v1.2.3.md) | Stable release notes |
26+
| [releases/v1.2.4.md](releases/v1.2.4.md) | Stable release notes |
2727
| [releases/v1.2.2.md](releases/v1.2.2.md) | Previous stable release notes |
2828
| [releases/v1.2.1.md](releases/v1.2.1.md) | Earlier stable release notes |
2929
| [releases/v1.2.0.md](releases/v1.2.0.md) | Archived stable release notes |
@@ -52,7 +52,7 @@ Public documentation for `codex-multi-auth`.
5252
| [reference/storage-paths.md](reference/storage-paths.md) | Canonical and compatibility storage paths |
5353
| [reference/public-api.md](reference/public-api.md) | Public API stability and semver contract |
5454
| [reference/error-contracts.md](reference/error-contracts.md) | CLI, JSON, and helper error semantics |
55-
| [releases/v1.2.3.md](releases/v1.2.3.md) | Current stable release notes |
55+
| [releases/v1.2.4.md](releases/v1.2.4.md) | Current stable release notes |
5656
| [releases/v0.1.0-beta.0.md](releases/v0.1.0-beta.0.md) | Archived prerelease reference |
5757
| [Daily Use release notes](#daily-use) | Stable, previous, and archived release notes |
5858
| [releases/legacy-pre-0.1-history.md](releases/legacy-pre-0.1-history.md) | Archived pre-0.1 changelog history |

docs/releases/v1.2.4.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Release v1.2.4
2+
3+
Release line: `stable`
4+
5+
This patch release follows the already-published `v1.2.3` rebuild and lands the post-merge review fixes that were raised afterward.
6+
7+
## Scope
8+
9+
- Current package version in `package.json` is `1.2.4`.
10+
- Canonical command family remains `codex auth ...`.
11+
- Canonical package name remains `codex-multi-auth`.
12+
- This patch is prepared from `main` after merge commit `c1da059852d698fd53014b9b529eeeeb2db1d39d`.
13+
14+
## What Changed
15+
16+
- preserved standalone `config.json` values when unified settings are malformed, and hardened unified-settings writes so invalid primaries can be rebuilt safely without masking real unreadable-file errors
17+
- retried flagged-account primary, backup, and legacy reads before fallback so transient Windows file locks do not trigger unnecessary recovery paths
18+
- avoided capability-policy failure penalties on fallback stream-failover `429` responses
19+
- retried shadow-home sync-back renames in the Codex wrapper so transient `EBUSY` and `EPERM` locks do not drop auth-state sync
20+
- removed scheduler-fragile midpoint assertions from quota-refresh CLI tests and tightened dashboard/unified-settings regressions around the real legacy/unified read paths
21+
22+
## Validation
23+
24+
- `npm run lint`
25+
- `npm run typecheck`
26+
- `npm run build`
27+
- `npm test`
28+
- Full suite passed: `222/222` files, `3307/3307` tests
29+
30+
## Related
31+
32+
- [v1.2.3.md](v1.2.3.md)
33+
- [../getting-started.md](../getting-started.md)
34+
- [../upgrade.md](../upgrade.md)
35+
- [../reference/commands.md](../reference/commands.md)

index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2192,11 +2192,11 @@ export const OpenAIOAuthPlugin: Plugin = async ({ client }: PluginInput) => {
21922192
modelFamily,
21932193
model,
21942194
);
2195+
capabilityPolicyStore.recordFailure(
2196+
fallbackEntitlementAccountKey,
2197+
capabilityModelKey,
2198+
);
21952199
}
2196-
capabilityPolicyStore.recordFailure(
2197-
fallbackEntitlementAccountKey,
2198-
capabilityModelKey,
2199-
);
22002200
continue;
22012201
}
22022202

lib/config.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -647,10 +647,7 @@ export async function savePluginConfig(
647647
: loadUnifiedPluginConfigSync();
648648
const unifiedConfig = sanitizeStoredPluginConfigRecord(unifiedConfigRecord);
649649
const legacyPath =
650-
unifiedConfigState.status === "missing" ||
651-
(unifiedConfigState.status === "ok" && !unifiedConfig)
652-
? resolvePluginConfigPath()
653-
: null;
650+
unifiedConfig === null ? resolvePluginConfigPath() : null;
654651
const legacyConfigState = legacyPath
655652
? await readConfigRecordForSave(legacyPath)
656653
: null;

lib/storage/flagged-storage-file.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ function isRetryableReadError(error: unknown): boolean {
88
return typeof code === "string" && RETRYABLE_READ_CODES.has(code);
99
}
1010

11-
async function readFileWithRetry(
11+
export async function readFileWithRetry(
1212
path: string,
1313
deps: {
1414
readFile: typeof import("node:fs").promises.readFile;

lib/storage/flagged-storage-io.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { existsSync, promises as fs } from "node:fs";
22
import { dirname } from "node:path";
33
import type { FlaggedAccountStorageV1 } from "../storage.js";
4+
import { readFileWithRetry } from "./flagged-storage-file.js";
45

56
const RETRYABLE_UNLINK_CODES = new Set(["EBUSY", "EAGAIN", "EPERM"]);
67

@@ -75,7 +76,9 @@ export async function loadFlaggedAccountsState(params: {
7576
continue;
7677
}
7778
try {
78-
const backupContent = await fs.readFile(backupPath, "utf-8");
79+
const backupContent = await readFileWithRetry(backupPath, {
80+
readFile: fs.readFile,
81+
});
7982
const backupData = JSON.parse(backupContent) as unknown;
8083
const recovered = params.normalizeFlaggedStorage(backupData);
8184
if (!isValidFlaggedStorageCandidate(backupData, recovered)) {
@@ -103,6 +106,7 @@ export async function loadFlaggedAccountsState(params: {
103106
to: params.path,
104107
error: String(persistError),
105108
});
109+
return recovered;
106110
}
107111
}
108112
params.logInfo("Recovered flagged account storage from backup", {
@@ -123,7 +127,9 @@ export async function loadFlaggedAccountsState(params: {
123127
};
124128

125129
try {
126-
const content = await fs.readFile(params.path, "utf-8");
130+
const content = await readFileWithRetry(params.path, {
131+
readFile: fs.readFile,
132+
});
127133
const data = JSON.parse(content) as unknown;
128134
const loaded = params.normalizeFlaggedStorage(data);
129135
if (!isValidFlaggedStorageCandidate(data, loaded)) {
@@ -154,7 +160,9 @@ export async function loadFlaggedAccountsState(params: {
154160
}
155161

156162
try {
157-
const legacyContent = await fs.readFile(params.legacyPath, "utf-8");
163+
const legacyContent = await readFileWithRetry(params.legacyPath, {
164+
readFile: fs.readFile,
165+
});
158166
const legacyData = JSON.parse(legacyContent) as unknown;
159167
const migrated = params.normalizeFlaggedStorage(legacyData);
160168
if (migrated.accounts.length > 0) {

lib/unified-settings.ts

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,16 @@ function cloneRecord(value: unknown): JsonRecord | null {
5353
return { ...value };
5454
}
5555

56+
class InvalidSettingsRecordError extends Error {
57+
override readonly name = "InvalidSettingsRecordError";
58+
}
59+
5660
function parseSettingsRecord(content: string): JsonRecord {
5761
const parsed = cloneRecord(JSON.parse(content));
5862
if (!parsed) {
59-
throw new Error("Unified settings must contain a JSON object at the root.");
63+
throw new InvalidSettingsRecordError(
64+
"Unified settings must contain a JSON object at the root.",
65+
);
6066
}
6167
return parsed;
6268
}
@@ -98,24 +104,34 @@ async function readSettingsRecordAsyncFromPath(
98104
* Best-effort backup reader for sync callers.
99105
*
100106
* Backup corruption is treated as an unavailable backup so callers can keep
101-
* their legacy null-on-unavailable behavior.
107+
* their legacy null-on-unavailable behavior, but unreadable or locked backups
108+
* still surface so writers do not rebuild from `{}` over a transient failure.
102109
*/
103110
function readSettingsBackupSync(): JsonRecord | null {
104111
try {
105112
return readSettingsRecordSyncFromPath(UNIFIED_SETTINGS_BACKUP_PATH);
106-
} catch {
107-
return null;
113+
} catch (error) {
114+
if (isInvalidSettingsRecordError(error)) {
115+
return null;
116+
}
117+
throw error;
108118
}
109119
}
110120

111121
/**
112122
* Best-effort backup reader for async callers.
123+
*
124+
* Like the sync variant, only corrupt backups are collapsed to `null`.
125+
* Unreadable or locked backups are rethrown so callers can fail closed.
113126
*/
114127
async function readSettingsBackupAsync(): Promise<JsonRecord | null> {
115128
try {
116129
return await readSettingsRecordAsyncFromPath(UNIFIED_SETTINGS_BACKUP_PATH);
117-
} catch {
118-
return null;
130+
} catch (error) {
131+
if (isInvalidSettingsRecordError(error)) {
132+
return null;
133+
}
134+
throw error;
119135
}
120136
}
121137

@@ -143,6 +159,13 @@ function shouldFallbackToSettingsBackup(
143159
return true;
144160
}
145161

162+
function isInvalidSettingsRecordError(error: unknown): boolean {
163+
if (error instanceof SyntaxError) {
164+
return true;
165+
}
166+
return error instanceof InvalidSettingsRecordError;
167+
}
168+
146169
/**
147170
* Snapshot the primary settings file into `settings.json.bak` for sync writes.
148171
*
@@ -221,6 +244,9 @@ function readSettingsRecordSyncInternal(): SettingsReadResult {
221244
if (backupRecord) {
222245
return { record: backupRecord, usedBackup: true };
223246
}
247+
if (isInvalidSettingsRecordError(error)) {
248+
return { record: null, usedBackup: false };
249+
}
224250
throw error;
225251
}
226252

@@ -255,6 +281,9 @@ async function readSettingsRecordAsyncInternal(): Promise<SettingsReadResult> {
255281
if (backupRecord) {
256282
return { record: backupRecord, usedBackup: true };
257283
}
284+
if (isInvalidSettingsRecordError(error)) {
285+
return { record: null, usedBackup: false };
286+
}
258287
throw error;
259288
}
260289

package-lock.json

Lines changed: 2 additions & 2 deletions
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": "1.2.3",
3+
"version": "1.2.4",
44
"description": "Multi-account OAuth manager and codex auth wrapper for the official @openai/codex CLI, with switching, health checks, and recovery tools",
55
"main": "./dist/index.js",
66
"types": "./dist/index.d.ts",

0 commit comments

Comments
 (0)