From 3895f20141af1c91b6b8444570b50251e1dad71f Mon Sep 17 00:00:00 2001 From: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Date: Sat, 2 May 2026 11:11:17 +0000 Subject: [PATCH] fix(discord): harden Carbon parity --- .../discord/src/internal/rest-scheduler.ts | 3 ++ extensions/discord/src/internal/rest.test.ts | 54 +++++++++++++++++++ extensions/discord/src/internal/rest.ts | 13 +---- 3 files changed, 59 insertions(+), 11 deletions(-) diff --git a/extensions/discord/src/internal/rest-scheduler.ts b/extensions/discord/src/internal/rest-scheduler.ts index dc7eb3a12d4..c342e144421 100644 --- a/extensions/discord/src/internal/rest-scheduler.ts +++ b/extensions/discord/src/internal/rest-scheduler.ts @@ -433,6 +433,9 @@ export class RestScheduler { lane: RequestPriority, now: number, ): void { + if (lane !== "background") { + return; + } const staleAfterMs = this.options.lanes[lane].staleAfterMs; if (!staleAfterMs || staleAfterMs <= 0) { return; diff --git a/extensions/discord/src/internal/rest.test.ts b/extensions/discord/src/internal/rest.test.ts index a8c8b32ec5b..d954600dafd 100644 --- a/extensions/discord/src/internal/rest.test.ts +++ b/extensions/discord/src/internal/rest.test.ts @@ -111,6 +111,60 @@ describe("RequestClient", () => { ); }); + it("keeps standard mutations queued until Discord accepts or rejects them", async () => { + vi.useFakeTimers(); + vi.setSystemTime(0); + const firstResponse = createDeferred(); + const fetchSpy = vi.fn(async () => + fetchSpy.mock.calls.length === 1 + ? await firstResponse.promise + : createJsonResponse({ ok: true }), + ); + const client = new RequestClient("test-token", { + fetch: fetchSpy, + scheduler: { + maxConcurrency: 1, + lanes: { + background: { staleAfterMs: 50 }, + standard: { staleAfterMs: 50 }, + }, + }, + }); + + const requests = [ + client.post("/channels/c1/messages", { body: { content: "send" } }), + client.patch("/channels/c1/messages/m1", { body: { content: "edit" } }), + client.delete("/channels/c1/messages/m2"), + client.post("/webhooks/app/token", { body: { content: "webhook send" } }), + client.patch("/webhooks/app/token/messages/@original", { + body: { content: "webhook edit" }, + }), + client.delete("/webhooks/app/token/messages/@original"), + client.post("/applications/app/commands", { body: { name: "ping" } }), + ]; + await vi.waitFor(() => expect(fetchSpy).toHaveBeenCalledTimes(1)); + + await vi.advanceTimersByTimeAsync(51); + firstResponse.resolve(createJsonResponse({ ok: true })); + + await expect(Promise.all(requests)).resolves.toEqual([ + { ok: true }, + { ok: true }, + { ok: true }, + { ok: true }, + { ok: true }, + { ok: true }, + { ok: true }, + ]); + expect(fetchSpy).toHaveBeenCalledTimes(requests.length); + expect(client.getSchedulerMetrics()).toEqual( + expect.objectContaining({ + droppedByLane: expect.objectContaining({ standard: 0 }), + queueSize: 0, + }), + ); + }); + it("runs independent route buckets concurrently", async () => { const channelResponse = createDeferred(); const guildResponse = createDeferred(); diff --git a/extensions/discord/src/internal/rest.ts b/extensions/discord/src/internal/rest.ts index 66eeffe6d6b..cba81bbcf19 100644 --- a/extensions/discord/src/internal/rest.ts +++ b/extensions/discord/src/internal/rest.ts @@ -72,7 +72,7 @@ const defaultOptions = { const DEFAULT_MAX_CONCURRENT_WORKERS = 4; const defaultLaneOptions: Record = { critical: { weight: 6 }, - standard: { staleAfterMs: 60_000, weight: 3 }, + standard: { weight: 3 }, background: { staleAfterMs: 20_000, weight: 1 }, }; @@ -318,14 +318,5 @@ function getRequestPriority(method: string, path: string): RestRequestPriority { if (/^\/interactions\/\d+\/[^/]+\/callback$/.test(normalizedPath)) { return "critical"; } - if ( - normalizedPath.startsWith("/webhooks/") && - (normalizedMethod === "POST" || normalizedMethod === "PATCH" || normalizedMethod === "DELETE") - ) { - return "standard"; - } - if (normalizedMethod !== "GET" && /\/channels\/\d+\/messages/.test(normalizedPath)) { - return "standard"; - } - return "background"; + return normalizedMethod === "GET" ? "background" : "standard"; }