diff --git a/src/gateway/server-methods/models-auth-status-usage-cache.ts b/src/gateway/server-methods/models-auth-status-usage-cache.ts index ffc8232394ef..353bfad64b7b 100644 --- a/src/gateway/server-methods/models-auth-status-usage-cache.ts +++ b/src/gateway/server-methods/models-auth-status-usage-cache.ts @@ -108,8 +108,12 @@ function retainLastGoodOnTimeout( .filter((provider) => provider.error === undefined) .map((provider) => [provider.provider, provider]), ); + const retainedLastGood = summary.providers.some( + (provider) => provider.error === "Timeout" && lastGoodByProvider.has(provider.provider), + ); return { ...summary, + updatedAt: retainedLastGood ? lastGood.updatedAt : summary.updatedAt, providers: summary.providers.map((provider) => provider.error === "Timeout" ? (lastGoodByProvider.get(provider.provider) ?? provider) diff --git a/src/gateway/server-methods/models-auth-status.test.ts b/src/gateway/server-methods/models-auth-status.test.ts index 1b2d834e5e3d..a0ecd6cdc9f3 100644 --- a/src/gateway/server-methods/models-auth-status.test.ts +++ b/src/gateway/server-methods/models-auth-status.test.ts @@ -1286,7 +1286,7 @@ describe("models.authStatus", () => { expect(warmed.providers[0]?.usage?.windows[0]?.usedPercent).toBe(10); }); - setPreparedAuthStore({ + const rotatedStore: AuthProfileStore = { version: 1, profiles: { "openai:default": { @@ -1297,10 +1297,16 @@ describe("models.authStatus", () => { expires: 1_000_000, }, }, - }); + }; + // Prepared catalog refresh can replace its owner before the ambient snapshot revision advances. + preparedAuthStore = rotatedStore; const rotated = await readAuthStatus(); + expect(mocks.buildAuthHealthSummary.mock.calls.at(-1)?.[0].store).toBe(rotatedStore); expect(rotated.providers[0]?.usage).toBeUndefined(); expect(mocks.loadProviderUsageSummary).toHaveBeenCalledTimes(2); + expect(mocks.loadProviderUsageSummary).toHaveBeenLastCalledWith( + expect.objectContaining({ authStore: rotatedStore }), + ); }); it("does not reuse usage after a direct provider key rotates", async () => { diff --git a/src/gateway/server-methods/provider-usage-runtime.ts b/src/gateway/server-methods/provider-usage-runtime.ts index b6691c2d8683..77857680a481 100644 --- a/src/gateway/server-methods/provider-usage-runtime.ts +++ b/src/gateway/server-methods/provider-usage-runtime.ts @@ -115,6 +115,8 @@ export function getProviderUsageRuntimeSnapshot(params: { const authStoreGeneration = getRuntimeAuthProfileStoreSnapshotRevision(agentDir); if ( current?.configRef === configRef && + // Prepared owners can advance before the ambient snapshot; their exact store fences reuse. + (params.store === undefined || current.store === params.store) && current.agentDir === agentDir && current.agentId === agentId && current.pluginRegistryGeneration === pluginRegistryGeneration && diff --git a/src/gateway/server-methods/usage.status-cache.test.ts b/src/gateway/server-methods/usage.status-cache.test.ts index f8c8acdf5aea..547b47e2e5b9 100644 --- a/src/gateway/server-methods/usage.status-cache.test.ts +++ b/src/gateway/server-methods/usage.status-cache.test.ts @@ -226,10 +226,11 @@ describe("usage.status provider usage cache", () => { const stale = await runUsageStatus(); expect(JSON.stringify(stale)).toBe(JSON.stringify(first)); await mocks.loadProviderUsageSummary.mock.results[1]?.value; + now = 62_000; await vi.waitFor(async () => { const retained = (await runUsageStatus()) as UsageSummary; expect(retained.providers).toEqual(first.providers); - expect(retained.updatedAt).toBe(61_000); + expect(retained.updatedAt).toBe(first.updatedAt); expect(mocks.loadProviderUsageSummary).toHaveBeenCalledTimes(2); }); });