From 6175bbd56827e40d9c13dce71cc723b3d6d671b2 Mon Sep 17 00:00:00 2001 From: andrewwbuilds Date: Sat, 3 Oct 2026 02:05:13 -0700 Subject: [PATCH 1/2] fix(server): a mid-turn usage update no longer hides a failed probe applyUsageLimitsUpdate rebuilt the snapshot without `unavailable`, so a single streamed window landing on a failed probe, or on no probe yet, was published as the account's complete limits. The next failed probe then kept that partial list as the last good one. The merge now carries `probeFailed` forward, and marks a merge onto nothing as `probeFailed`, until a probe reads the account. An unchanged update still returns the same snapshot. Refs: #12170 --- .../makeManagedServerProvider.test.ts | 55 +++++++++++++++++++ .../src/provider/providerUsageLimits.test.ts | 28 ++++++++++ .../src/provider/providerUsageLimits.ts | 10 +++- 3 files changed, 92 insertions(+), 1 deletion(-) diff --git a/apps/server/src/provider/makeManagedServerProvider.test.ts b/apps/server/src/provider/makeManagedServerProvider.test.ts index 8b6133d5c523..3be5f2499074 100644 --- a/apps/server/src/provider/makeManagedServerProvider.test.ts +++ b/apps/server/src/provider/makeManagedServerProvider.test.ts @@ -657,4 +657,59 @@ describe("makeManagedServerProvider", () => { }), ).pipe(Effect.provide(AlwaysRunTestLayer)), ); + + it.effect("does not publish a window streamed after a failed probe as the full list", () => + Effect.scoped( + Effect.gen(function* () { + const refreshCount = yield* Ref.make(0); + const failedLimits = { + checkedAt: "2026-04-10T00:00:01.000Z", + windows: [], + unavailable: { reason: "probeFailed" }, + } as const; + const probedLimits = { + checkedAt: "2026-04-10T00:00:03.000Z", + windows: [ + { id: "five_hour", kind: "session", label: "Session", usedPercent: 10 }, + { id: "seven_day", kind: "weekly", label: "Weekly", usedPercent: 25 }, + ], + } as const; + const provider = yield* makeManagedServerProvider({ + resolveMaintenance: () => Effect.succeed(maintenanceCapabilities), + getSettings: Effect.succeed({ enabled: true }), + streamSettings: Stream.empty, + haveSettingsChanged: (previous, next) => previous.enabled !== next.enabled, + initialSnapshot: () => Effect.succeed(initialSnapshot), + checkProvider: Ref.updateAndGet(refreshCount, (count) => count + 1).pipe( + Effect.map((count) => ({ + ...refreshedSnapshot, + usageLimits: count < 3 ? failedLimits : probedLimits, + })), + ), + refreshInterval: "1 hour", + }); + yield* Stream.take(provider.streamChanges, 1).pipe(Stream.runDrain); + + const weekly = { + id: "seven_day", + kind: "weekly", + label: "Weekly", + usedPercent: 20, + } as const; + yield* provider.applyUsageLimits({ + checkedAt: "2026-04-10T00:00:02.000Z", + windows: [weekly], + }); + assert.deepStrictEqual((yield* provider.getSnapshot).usageLimits, { + checkedAt: "2026-04-10T00:00:02.000Z", + windows: [weekly], + unavailable: { reason: "probeFailed" }, + }); + + // A second failed probe has no last good list to keep. + assert.deepStrictEqual((yield* provider.refresh).usageLimits, failedLimits); + assert.deepStrictEqual((yield* provider.refresh).usageLimits, probedLimits); + }), + ).pipe(Effect.provide(AlwaysRunTestLayer)), + ); }); diff --git a/apps/server/src/provider/providerUsageLimits.test.ts b/apps/server/src/provider/providerUsageLimits.test.ts index 6e288ddd3a36..a6d9a5c5dfd7 100644 --- a/apps/server/src/provider/providerUsageLimits.test.ts +++ b/apps/server/src/provider/providerUsageLimits.test.ts @@ -62,6 +62,34 @@ describe("applyUsageLimitsUpdate", () => { ).toBe(published); }); + it("keeps a failed probe marked when a sparse update lands on it", () => { + const failed = { + checkedAt, + windows: [], + unavailable: { reason: "probeFailed" as const, message: "Codex did not answer." }, + }; + const next = applyUsageLimitsUpdate({ + previous: failed, + checkedAt: "2026-09-03T12:00:05.000Z", + update: { windows: [weekly] }, + }); + expect(next).toEqual({ + checkedAt: "2026-09-03T12:00:05.000Z", + windows: [weekly], + unavailable: failed.unavailable, + }); + // The same numbers again must not rebuild the snapshot and publish it. + expect( + applyUsageLimitsUpdate({ previous: next, checkedAt, update: { windows: [weekly] } }), + ).toBe(next); + }); + + it("marks a sparse update with nothing published as a failed read", () => { + expect( + applyUsageLimitsUpdate({ previous: undefined, checkedAt, update: { windows: [weekly] } }), + ).toEqual({ checkedAt, windows: [weekly], unavailable: { reason: "probeFailed" } }); + }); + it("preserves reset credits when a streamed window update changes usage", () => { const resetCredits = { availableCount: 2, nextExpiresAt: "2026-10-01T00:00:00.000Z" }; const next = applyUsageLimitsUpdate({ diff --git a/apps/server/src/provider/providerUsageLimits.ts b/apps/server/src/provider/providerUsageLimits.ts index ea8d0d1d029f..85d09b9fec19 100644 --- a/apps/server/src/provider/providerUsageLimits.ts +++ b/apps/server/src/provider/providerUsageLimits.ts @@ -56,6 +56,11 @@ export function makeUnavailableUsageLimits(input: { * * An `unsupported` snapshot stays unsupported: an account that cannot have * subscription windows will not start reporting them mid-turn. + * + * An update never completes a read. Merged onto a failed probe, or onto no + * probe at all, it keeps the windows it names but leaves the snapshot + * `probeFailed`. Otherwise one streamed window would read as the account's + * full list, and the next failed probe would keep it as the last good one. */ export function applyUsageLimitsUpdate(input: { readonly previous: ServerProviderUsageLimits | undefined; @@ -88,12 +93,15 @@ export function applyUsageLimitsUpdate(input: { changed = true; } } - if (!changed && previous !== undefined && previous.unavailable === undefined) { + if (!changed && previous !== undefined) { return previous; } + const unavailable = + previous === undefined ? { reason: "probeFailed" as const } : previous.unavailable; return { ...makeUsageLimits({ checkedAt: input.checkedAt, windows: merged.values() }), ...(previous?.resetCredits !== undefined ? { resetCredits: previous.resetCredits } : {}), + ...(unavailable !== undefined ? { unavailable } : {}), }; } From d62c03f5aa6ef5c7ae51291a57abd70b7f4a5846 Mon Sep 17 00:00:00 2001 From: andrewwbuilds Date: Sat, 10 Oct 2026 03:30:43 -0500 Subject: [PATCH 2/2] test(provider-core): use the renamed always-run test layer Main renamed AlwaysRunTestLayer to layerAlwaysRunTest (#16282), and the merge moved this test into provider-core without picking up the new name. --- packages/provider-core/src/server/managedProvider.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/provider-core/src/server/managedProvider.test.ts b/packages/provider-core/src/server/managedProvider.test.ts index 300f24e858c2..7a41bc7698b2 100644 --- a/packages/provider-core/src/server/managedProvider.test.ts +++ b/packages/provider-core/src/server/managedProvider.test.ts @@ -710,6 +710,6 @@ describe("makeManagedServerProvider", () => { assert.deepStrictEqual((yield* provider.refresh).usageLimits, failedLimits); assert.deepStrictEqual((yield* provider.refresh).usageLimits, probedLimits); }), - ).pipe(Effect.provide(AlwaysRunTestLayer)), + ).pipe(Effect.provide(layerAlwaysRunTest)), ); });