From d6357e0524b1909362f70305e39c030170ea6343 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 4 Nov 2026 18:40:44 +0000 Subject: [PATCH 1/4] test(shared): reproduce #31066 spent OpenCode Go monthly quota leaving session and weekly usable These tests reproduce #31066: a spent go_monthly window does not mark the same account's go_rolling and go_weekly windows as spent in the pool. Co-Authored-By: Claude Opus 5.5 --- packages/shared/src/usageLimits.test.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/shared/src/usageLimits.test.ts b/packages/shared/src/usageLimits.test.ts index 72a17323386c8..25b27dd62612d 100644 --- a/packages/shared/src/usageLimits.test.ts +++ b/packages/shared/src/usageLimits.test.ts @@ -972,6 +972,29 @@ describe("pooled account columns", () => { expect(accounts).toEqual(snapshot); }); + it("treats OpenCode Go session and weekly quota as spent while the monthly quota is", () => { + const go = (key: string, rolling: number, weeklyUsed: number, monthly: number) => ({ + ...account(key, [ + { ...window, id: "go_rolling", usedPercent: rolling }, + { ...weekly, id: "go_weekly", usedPercent: weeklyUsed }, + { + ...window, + id: "go_monthly", + kind: "monthly", + usedPercent: monthly, + windowDurationMins: undefined, + resetsAt: "2026-09-20T12:00:00.000Z", + }, + ]), + driver: ProviderDriverKind.make("opencode"), + }); + const [pool] = collectLimitPools([go("a", 1, 24, 20), go("b", 0, 0, 100)], now); + expect + .soft(pool!.windows.map((row) => row.columns[1]?.window?.usedPercent)) + .toEqual([100, 100, 100]); + expect(pool!.windows.map((row) => row.remainingPercent)).toEqual([50, 38, 40]); + }); + it("keeps session columns across rows with opposite reset and usage orders", () => { const accounts = [ account("a", [ From d170b76e0314fd53fbc1e6df0b7e0b7bfa99b05e Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 4 Nov 2026 18:44:10 +0000 Subject: [PATCH 2/4] fix(usage): a spent OpenCode Go monthly quota empties its session and weekly bars poolMember only marked a session as spent behind an exhausted Claude or Codex weekly window, so an OpenCode Go account with go_monthly at 100% still showed usable rolling and weekly quota and inflated pooled headlines. Look up the spent window that blocks the reported one through a per-driver table (Claude/Codex: weekly blocks session; OpenCode: go_monthly blocks go_rolling and go_weekly) and keep the existing refill/reset logic. Fixes pingdotgg/t3code#31066 Co-Authored-By: Claude Opus 5.5 --- packages/shared/src/usageLimits.ts | 49 ++++++++++++++++-------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/packages/shared/src/usageLimits.ts b/packages/shared/src/usageLimits.ts index 869f676acae69..bec87b2c0c613 100644 --- a/packages/shared/src/usageLimits.ts +++ b/packages/shared/src/usageLimits.ts @@ -511,45 +511,50 @@ function poolKey(driver: ServerProvider["driver"], window: ServerProviderUsageWi return driver === "codex" ? window.kind : `${window.kind}:${window.id}`; } +// A spent blocker window leaves no usable quota in the windows it blocks. +const BLOCKS: Record< + string, + (blocker: ServerProviderUsageWindow, reported: ServerProviderUsageWindow) => boolean +> = { + claudeAgent: (blocker, reported) => reported.kind === "session" && blocker.id === "seven_day", + codex: (blocker, reported) => reported.kind === "session" && blocker.kind === "weekly", + opencode: (blocker, reported) => + blocker.id === "go_monthly" && (reported.id === "go_rolling" || reported.id === "go_weekly"), +}; + function poolMember(account: LimitAccount, reported: ServerProviderUsageWindow, now: number) { - const spentWeekly = - reported.kind === "session" - ? account.limits.windows.find( - (window) => - window.usedPercent >= 100 && - (account.driver === "claudeAgent" - ? window.id === "seven_day" - : account.driver === "codex" && window.kind === "weekly"), - ) - : undefined; - if (!spentWeekly) return { account, window: reported, refillPercent: reported.usedPercent }; - - const weeklyReset = resetMillis(spentWeekly); - const sessionReset = resetMillis(reported); + const blocks = BLOCKS[account.driver]; + const blocker = blocks + ? account.limits.windows.find((window) => window.usedPercent >= 100 && blocks(window, reported)) + : undefined; + if (!blocker) return { account, window: reported, refillPercent: reported.usedPercent }; + + const blockerReset = resetMillis(blocker); + const reportedReset = resetMillis(reported); const cutoff = Math.max(now, Date.parse(account.limits.checkedAt)); let resetsAt: string | undefined; let refillPercent = 0; // A stale or missing clock cannot tell us when blocked quota becomes usable. if ( - weeklyReset !== null && - sessionReset !== null && - weeklyReset > cutoff && - sessionReset > cutoff + blockerReset !== null && + reportedReset !== null && + blockerReset > cutoff && + reportedReset > cutoff ) { - if (sessionReset <= weeklyReset) { - resetsAt = spentWeekly.resetsAt; + if (reportedReset <= blockerReset) { + resetsAt = blocker.resetsAt; refillPercent = 100; } else if (reported.usedPercent >= 100) { resetsAt = reported.resetsAt; refillPercent = 100; } else { - resetsAt = spentWeekly.resetsAt; + resetsAt = blocker.resetsAt; refillPercent = 100 - Math.max(0, Math.min(100, reported.usedPercent)); } } return { account, - // Suppression is not session spending, so it has no session pace clock. + // Suppression is not spending in the reported window, so it has no pace clock. window: { ...reported, usedPercent: 100, resetsAt, windowDurationMins: undefined }, refillPercent, }; From 945c9efcb4dab49062dbad3a89814891e1566bf6 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 4 Nov 2026 18:55:25 +0000 Subject: [PATCH 3/4] fix(usage): a spent OpenCode Go weekly quota also empties its session bar Hold the session until the last spent longer quota resets when both the weekly and monthly Go quotas are spent. Co-Authored-By: Claude Opus 5.5 --- packages/shared/src/usageLimits.test.ts | 24 ++++++++++++++++++++++++ packages/shared/src/usageLimits.ts | 17 +++++++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/packages/shared/src/usageLimits.test.ts b/packages/shared/src/usageLimits.test.ts index 25b27dd62612d..eb9e8c67258d9 100644 --- a/packages/shared/src/usageLimits.test.ts +++ b/packages/shared/src/usageLimits.test.ts @@ -995,6 +995,30 @@ describe("pooled account columns", () => { expect(pool!.windows.map((row) => row.remainingPercent)).toEqual([50, 38, 40]); }); + it("holds an OpenCode Go session spent until the last spent longer quota resets", () => { + const monthlyReset = "2026-09-20T12:00:00.000Z"; + const go = (key: string, monthlyUsed: number) => ({ + ...account(key, [ + { ...window, id: "go_rolling", usedPercent: 0 }, + { ...weekly, id: "go_weekly", usedPercent: 100, resetsAt: "2026-09-05T12:00:00.000Z" }, + { + ...window, + id: "go_monthly", + kind: "monthly", + usedPercent: monthlyUsed, + windowDurationMins: undefined, + resetsAt: monthlyReset, + }, + ]), + driver: ProviderDriverKind.make("opencode"), + }); + const sessions = [go("weekly", 40), go("both", 100)].map( + (account) => collectLimitPools([account], now)[0]!.windows[0]!, + ); + expect.soft(sessions.map((session) => session.remainingPercent)).toEqual([0, 0]); + expect(sessions[1]!.resets[0]?.at).toBe(Date.parse(monthlyReset)); + }); + it("keeps session columns across rows with opposite reset and usage orders", () => { const accounts = [ account("a", [ diff --git a/packages/shared/src/usageLimits.ts b/packages/shared/src/usageLimits.ts index bec87b2c0c613..58e3c61e16c9a 100644 --- a/packages/shared/src/usageLimits.ts +++ b/packages/shared/src/usageLimits.ts @@ -519,13 +519,26 @@ const BLOCKS: Record< claudeAgent: (blocker, reported) => reported.kind === "session" && blocker.id === "seven_day", codex: (blocker, reported) => reported.kind === "session" && blocker.kind === "weekly", opencode: (blocker, reported) => - blocker.id === "go_monthly" && (reported.id === "go_rolling" || reported.id === "go_weekly"), + blocker.id === "go_monthly" + ? reported.id === "go_rolling" || reported.id === "go_weekly" + : blocker.id === "go_weekly" && reported.id === "go_rolling", }; function poolMember(account: LimitAccount, reported: ServerProviderUsageWindow, now: number) { const blocks = BLOCKS[account.driver]; + // With several spent blockers, quota returns only when the last of them resets. const blocker = blocks - ? account.limits.windows.find((window) => window.usedPercent >= 100 && blocks(window, reported)) + ? account.limits.windows + .filter((window) => window.usedPercent >= 100 && blocks(window, reported)) + .reduce( + (latest, window) => + latest && + (resetMillis(latest) ?? Number.POSITIVE_INFINITY) >= + (resetMillis(window) ?? Number.POSITIVE_INFINITY) + ? latest + : window, + undefined, + ) : undefined; if (!blocker) return { account, window: reported, refillPercent: reported.usedPercent }; From ca3f2137ba074201e3f329f72cf9946f060ff7da Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 4 Nov 2026 19:07:30 +0000 Subject: [PATCH 4/4] fix(usage): look up blocking windows by own driver key, cover a later weekly reset Co-Authored-By: Claude Opus 5.5 --- packages/shared/src/usageLimits.test.ts | 14 +++++++++----- packages/shared/src/usageLimits.ts | 23 +++++++++++++---------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/packages/shared/src/usageLimits.test.ts b/packages/shared/src/usageLimits.test.ts index eb9e8c67258d9..91d5f5ce54df4 100644 --- a/packages/shared/src/usageLimits.test.ts +++ b/packages/shared/src/usageLimits.test.ts @@ -997,10 +997,11 @@ describe("pooled account columns", () => { it("holds an OpenCode Go session spent until the last spent longer quota resets", () => { const monthlyReset = "2026-09-20T12:00:00.000Z"; - const go = (key: string, monthlyUsed: number) => ({ + const weeklyLater = "2026-09-25T12:00:00.000Z"; + const go = (key: string, monthlyUsed: number, weeklyReset = "2026-09-05T12:00:00.000Z") => ({ ...account(key, [ { ...window, id: "go_rolling", usedPercent: 0 }, - { ...weekly, id: "go_weekly", usedPercent: 100, resetsAt: "2026-09-05T12:00:00.000Z" }, + { ...weekly, id: "go_weekly", usedPercent: 100, resetsAt: weeklyReset }, { ...window, id: "go_monthly", @@ -1012,11 +1013,14 @@ describe("pooled account columns", () => { ]), driver: ProviderDriverKind.make("opencode"), }); - const sessions = [go("weekly", 40), go("both", 100)].map( + const sessions = [go("weekly", 40), go("both", 100), go("weekly-later", 100, weeklyLater)].map( (account) => collectLimitPools([account], now)[0]!.windows[0]!, ); - expect.soft(sessions.map((session) => session.remainingPercent)).toEqual([0, 0]); - expect(sessions[1]!.resets[0]?.at).toBe(Date.parse(monthlyReset)); + expect.soft(sessions.map((session) => session.remainingPercent)).toEqual([0, 0, 0]); + expect(sessions.slice(1).map((session) => session.resets[0]?.at)).toEqual([ + Date.parse(monthlyReset), + Date.parse(weeklyLater), + ]); }); it("keeps session columns across rows with opposite reset and usage orders", () => { diff --git a/packages/shared/src/usageLimits.ts b/packages/shared/src/usageLimits.ts index 58e3c61e16c9a..ef28ab4af79c2 100644 --- a/packages/shared/src/usageLimits.ts +++ b/packages/shared/src/usageLimits.ts @@ -512,20 +512,23 @@ function poolKey(driver: ServerProvider["driver"], window: ServerProviderUsageWi } // A spent blocker window leaves no usable quota in the windows it blocks. -const BLOCKS: Record< +const BLOCKS = new Map< string, (blocker: ServerProviderUsageWindow, reported: ServerProviderUsageWindow) => boolean -> = { - claudeAgent: (blocker, reported) => reported.kind === "session" && blocker.id === "seven_day", - codex: (blocker, reported) => reported.kind === "session" && blocker.kind === "weekly", - opencode: (blocker, reported) => - blocker.id === "go_monthly" - ? reported.id === "go_rolling" || reported.id === "go_weekly" - : blocker.id === "go_weekly" && reported.id === "go_rolling", -}; +>([ + ["claudeAgent", (blocker, reported) => reported.kind === "session" && blocker.id === "seven_day"], + ["codex", (blocker, reported) => reported.kind === "session" && blocker.kind === "weekly"], + [ + "opencode", + (blocker, reported) => + blocker.id === "go_monthly" + ? reported.id === "go_rolling" || reported.id === "go_weekly" + : blocker.id === "go_weekly" && reported.id === "go_rolling", + ], +]); function poolMember(account: LimitAccount, reported: ServerProviderUsageWindow, now: number) { - const blocks = BLOCKS[account.driver]; + const blocks = BLOCKS.get(account.driver); // With several spent blockers, quota returns only when the last of them resets. const blocker = blocks ? account.limits.windows