diff --git a/packages/trpc/src/router/cloud-workspace/meter.test.ts b/packages/trpc/src/router/cloud-workspace/meter.test.ts index 93d83baac5f..2d83eed4ec2 100644 --- a/packages/trpc/src/router/cloud-workspace/meter.test.ts +++ b/packages/trpc/src/router/cloud-workspace/meter.test.ts @@ -365,12 +365,13 @@ describe("settleBoot", () => { expect(autumnCalls.some((call) => call.method === "track")).toBe(false); }); - test("a settle whose next lock fails keeps the billed window, so a retry does not confirm it again", async () => { + test("a settle whose next lock fails keeps the billed window and moves to a new lock id for the retry", async () => { renewalError = new Error("autumn down"); await expect(settleBoot("boot_1")).rejects.toThrow("autumn down"); expect(writes).toContainEqual( expect.objectContaining({ lockOpen: false, + lockSeq: 1, finalizedThrough: expect.any(Date), }), ); diff --git a/packages/trpc/src/router/cloud-workspace/meter.ts b/packages/trpc/src/router/cloud-workspace/meter.ts index 20d33e560da..b569ba3f9e6 100644 --- a/packages/trpc/src/router/cloud-workspace/meter.ts +++ b/packages/trpc/src/router/cloud-workspace/meter.ts @@ -415,9 +415,15 @@ export async function settleBoot( amount: gbMinutes({ ms: end - from, memoryMb: session.memoryMb }), user, }); + const nextSeq = boot.lockSeq + 1; + // Autumn answers a repeated lock id with 409, so a retry after a failed renewal needs a fresh one. await db .update(cloudWorkspaceBoots) - .set({ finalizedThrough: new Date(end), lockOpen: false }) + .set({ + finalizedThrough: new Date(end), + lockOpen: false, + lockSeq: nextSeq, + }) .where(eq(cloudWorkspaceBoots.id, bootId)); if (session.endedAt !== null) { @@ -434,7 +440,6 @@ export async function settleBoot( return "closed"; } - const nextSeq = boot.lockSeq + 1; const renewal = await autumnClient.check({ customerId: boot.organizationId, featureId: BOX_GB_MINUTES_FEATURE_ID,