Skip to content
Open
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ codex-multi-auth doctor --json

## Release Notes

- Current prerelease: [docs/releases/v2.3.0-beta.0.md](docs/releases/v2.3.0-beta.0.md) — install via `npm i -g codex-multi-auth@beta`
- Current prerelease: [docs/releases/v2.3.0-beta.1.md](docs/releases/v2.3.0-beta.1.md) — install via `npm i -g codex-multi-auth@beta`
- Current stable: [docs/releases/v2.2.2.md](docs/releases/v2.2.2.md) — install via `npm i -g codex-multi-auth`
- Previous stable: [docs/releases/v2.2.1.md](docs/releases/v2.2.1.md)
- Previous stable: [docs/releases/v2.2.0.md](docs/releases/v2.2.0.md)
Expand Down
2 changes: 1 addition & 1 deletion lib/recovery/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -672,7 +672,7 @@ export function findEmptyMessageByIndex(
if (idx < 0 || idx >= messages.length) continue;

const targetMsg = messages[idx];
if (!targetMsg) continue;
if (!targetMsg || targetMsg.role !== "assistant") continue;

if (!messageHasContent(targetMsg.id)) {
return targetMsg.id;
Expand Down
2 changes: 1 addition & 1 deletion lib/rotation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ export interface HybridSelectionConfig {
healthWeight: number;
/** Weight for token count (default: 5) */
tokenWeight: number;
/** Weight for freshness/last used (default: 0.1) */
/** Weight for freshness/last used (default: 2) */
freshnessWeight: number;
}

Expand Down
4 changes: 3 additions & 1 deletion lib/storage/flagged-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,14 @@ export function normalizeFlaggedStorage(
value === "initial" ||
value === "rotation" ||
value === "best" ||
value === "restore";
value === "restore" ||
value === "manual";
const isCooldownReason = (
value: unknown,
): value is AccountMetadataV3["cooldownReason"] =>
value === "auth-failure" ||
value === "network-error" ||
value === "server-error" ||
value === "rate-limit";

let rateLimitResetTimes:
Expand Down
36 changes: 36 additions & 0 deletions test/flagged-storage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,40 @@ describe("flagged storage helper", () => {
expect(result.accounts[0]?.refreshToken).toBe("token-1");
expect(result.accounts[0]?.lastError).toBe("oops");
});

it("preserves the 'manual' last switch reason on round-trip", () => {
const result = normalizeFlaggedStorage(
{
version: 1,
accounts: [
{
refreshToken: "token-1",
flaggedAt: 10,
lastSwitchReason: "manual",
},
],
},
{ isRecord, now: () => 99 },
);

expect(result.accounts[0]?.lastSwitchReason).toBe("manual");
});

it("preserves the 'server-error' cooldown reason on round-trip", () => {
const result = normalizeFlaggedStorage(
{
version: 1,
accounts: [
{
refreshToken: "token-1",
flaggedAt: 10,
cooldownReason: "server-error",
},
],
},
{ isRecord, now: () => 99 },
);

expect(result.accounts[0]?.cooldownReason).toBe("server-error");
});
});
33 changes: 33 additions & 0 deletions test/fs-retry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { describe, expect, it } from "vitest";
import {
FILE_RETRY_CODES,
shouldRetryFileOperation,
} from "../lib/fs-retry.js";

function errnoError(code: string): NodeJS.ErrnoException {
const error = new Error(code) as NodeJS.ErrnoException;
error.code = code;
return error;
}

describe("shouldRetryFileOperation", () => {
it("treats every transient lock code as retryable", () => {
for (const code of ["EBUSY", "EPERM", "EAGAIN", "ENOTEMPTY", "EACCES"]) {
expect(shouldRetryFileOperation(errnoError(code)), code).toBe(true);
expect(FILE_RETRY_CODES.has(code), code).toBe(true);
}
});

it("does not retry non-transient errors", () => {
for (const code of ["ENOENT", "EISDIR", "EINVAL", "EROFS"]) {
expect(shouldRetryFileOperation(errnoError(code)), code).toBe(false);
}
});

it("returns false for non-errors and errors without a code", () => {
expect(shouldRetryFileOperation(undefined)).toBe(false);
expect(shouldRetryFileOperation(null)).toBe(false);
expect(shouldRetryFileOperation("EACCES")).toBe(false);
expect(shouldRetryFileOperation(new Error("no code"))).toBe(false);
});
});