fix(ai): reconciled codex self-heal with shared-scoped blocks

- markUsageLimitReached persists codex blocks under the strategy blockScope
  ("shared" since 0e259bb640); the healthy-usage reconcile guard read only
  unscoped state, so the self-heal from PR #4844 never fired on current main.
- Guard now resolves the ranking-strategy blockScope like selection does.
- Updated PR tests to write/read shared-scoped blocks.
This commit is contained in:
can1357
2026-07-08 16:59:59 +02:00
parent d43b0e2255
commit 3f0c2c63a0
2 changed files with 18 additions and 16 deletions
+7 -5
View File
@@ -4372,10 +4372,9 @@ export class AuthStorage {
/**
* Self-heal a stale Codex usage-limit block: when a fresh live usage report
* shows the account is allowed and below every limit, drop the persisted and
* in-memory `openai-codex:oauth` block so the balancer re-includes it. Only
* Codex — its blocks are unscoped (`codexRankingStrategy` defines no
* `blockScope`), so clearing all blocks for the credential id is exact. A
* future scoped Codex block would require revisiting this.
* in-memory `openai-codex:oauth` blocks so the balancer re-includes it. Only
* Codex — its ranking strategy uses the single model-independent `"shared"`
* scope, so clearing every block for the credential id is exact.
*/
#isHealthyCodexUsageReport(report: UsageReport): boolean {
const metadata = report.metadata;
@@ -4392,7 +4391,10 @@ export class AuthStorage {
const providerKey = this.#getProviderTypeKey(provider, "oauth");
const credentialIndex = this.#getStoredCredentials(provider).findIndex(entry => entry.id === credentialId);
if (credentialIndex < 0) return;
const blockedUntilMs = this.#getCredentialBlockedUntil(provider, providerKey, credentialIndex);
// Mirror selection: consult the same strategy scope `markUsageLimitReached`
// persists under, else a scoped block is invisible here and never healed.
const blockScope = this.#rankingStrategyResolver?.(provider)?.blockScope?.({});
const blockedUntilMs = this.#getCredentialBlockedUntil(provider, providerKey, credentialIndex, blockScope);
if (blockedUntilMs === undefined) return;
this.#clearCredentialBlocks(provider, credentialId);
logger.info("Cleared stale Codex usage-limit block after healthy live usage report", {
@@ -350,7 +350,7 @@ describe("AuthStorage codex oauth ranking", () => {
store.upsertCredentialBlock({
credentialId: blockedRow.id,
providerKey: "openai-codex:oauth",
blockScope: "",
blockScope: "shared",
blockedUntilMs: Date.now() + 6 * 24 * HOUR_MS,
});
@@ -398,7 +398,7 @@ describe("AuthStorage codex oauth ranking", () => {
await authStorage.fetchUsageReports();
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "")).toBeUndefined();
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "shared")).toBeUndefined();
expect(authStorage.getGeneration()).toBeGreaterThan(generationBeforeFetch);
const reconciledSelectionCounts = await countApiKeySelections(
@@ -483,19 +483,19 @@ describe("AuthStorage codex oauth ranking", () => {
baseUrlResolver: provider => (provider === "openai-codex" ? inFlightBaseUrl : undefined),
});
await inFlightStarted.promise;
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "")).toBeUndefined();
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "shared")).toBeUndefined();
const markResult = await authStorage.markUsageLimitReached("openai-codex", blockedSessionId, {
retryAfterMs: 6 * 24 * HOUR_MS,
});
expect(markResult.switched).toBe(true);
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "")).toBeDefined();
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "shared")).toBeDefined();
inFlightUsage.resolve(blockedReport);
await inFlightReports;
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "")).toBeDefined();
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "shared")).toBeDefined();
const selectionCounts = await countApiKeySelections(
authStorage,
"openai-codex",
@@ -599,14 +599,14 @@ describe("AuthStorage codex oauth ranking", () => {
});
expect(markResult.switched).toBe(true);
expect(remoteStore.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "")).toBeDefined();
expect(remoteStore.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "shared")).toBeDefined();
await blockPersisted.promise;
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "")).toBeDefined();
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "shared")).toBeDefined();
await clientStorage.fetchUsageReports();
expect(remoteStore.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "")).toBeUndefined();
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "")).toBeUndefined();
expect(remoteStore.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "shared")).toBeUndefined();
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "shared")).toBeUndefined();
expect(await clientStorage.getApiKey("openai-codex", blockedSessionId)).toBe("api-acct-broker-blocked");
} finally {
clientStorage.close();
@@ -637,7 +637,7 @@ describe("AuthStorage codex oauth ranking", () => {
store.upsertCredentialBlock({
credentialId: blockedRow.id,
providerKey: "openai-codex:oauth",
blockScope: "",
blockScope: "shared",
blockedUntilMs,
});
@@ -674,7 +674,7 @@ describe("AuthStorage codex oauth ranking", () => {
await authStorage.fetchUsageReports();
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "")).toBe(blockedUntilMs);
expect(store.getCredentialBlock(blockedRow.id, "openai-codex:oauth", "shared")).toBe(blockedUntilMs);
});
test("falls back to earliest-unblocking account when all exhausted", async () => {