From 9134dbd2525a04d32abd8375a33df444ecb3a733 Mon Sep 17 00:00:00 2001 From: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> Date: Fri, 27 Mar 2026 16:20:02 -0500 Subject: [PATCH] fix(regression): fail discord startup on reconnect exhaustion --- .../src/monitor/provider.lifecycle.test.ts | 31 +++++++++++++++++++ .../discord/src/monitor/provider.lifecycle.ts | 10 ++++-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/extensions/discord/src/monitor/provider.lifecycle.test.ts b/extensions/discord/src/monitor/provider.lifecycle.test.ts index 1c5e425869c..acb872385a4 100644 --- a/extensions/discord/src/monitor/provider.lifecycle.test.ts +++ b/extensions/discord/src/monitor/provider.lifecycle.test.ts @@ -855,6 +855,37 @@ describe("runDiscordGatewayLifecycle", () => { expect(runtimeError).toHaveBeenCalledWith(expect.stringContaining("Max reconnect attempts")); }); + it("rejects reconnect-exhausted queued before startup when shutdown has not begun", async () => { + const { runDiscordGatewayLifecycle } = await import("./provider.lifecycle.js"); + const pendingGatewayEvents: DiscordGatewayEvent[] = []; + + const emitter = new EventEmitter(); + const gateway: MockGateway = { + isConnected: true, + options: { intents: 0, reconnect: { maxAttempts: 50 } } as GatewayPlugin["options"], + disconnect: vi.fn(), + connect: vi.fn(), + emitter, + }; + getDiscordGatewayEmitterMock.mockReturnValueOnce(emitter); + + const { lifecycleParams } = createLifecycleHarness({ + gateway, + pendingGatewayEvents, + }); + + pendingGatewayEvents.push( + createGatewayEvent( + "reconnect-exhausted", + "Max reconnect attempts (0) reached after code 1005", + ), + ); + + await expect(runDiscordGatewayLifecycle(lifecycleParams)).rejects.toThrow( + "Max reconnect attempts", + ); + }); + it("does not push connected: true when abortSignal is already aborted", async () => { const { runDiscordGatewayLifecycle } = await import("./provider.lifecycle.js"); const emitter = new EventEmitter(); diff --git a/extensions/discord/src/monitor/provider.lifecycle.ts b/extensions/discord/src/monitor/provider.lifecycle.ts index 5b61ed2b483..c206cc60ba9 100644 --- a/extensions/discord/src/monitor/provider.lifecycle.ts +++ b/extensions/discord/src/monitor/provider.lifecycle.ts @@ -83,9 +83,13 @@ export async function runDiscordGatewayLifecycle(params: { return "continue"; } // Don't throw for expected shutdown events. `reconnect-exhausted` can be - // queued before teardown flips `lifecycleStopping`, so treat it as a - // graceful stop here and let the health monitor own reconnect behavior. - if (event.type === "disallowed-intents" || event.type === "reconnect-exhausted") { + // queued just before an abort-driven shutdown flips `lifecycleStopping`, + // so only suppress it when shutdown is already underway. + if ( + event.type === "disallowed-intents" || + (event.type === "reconnect-exhausted" && + (lifecycleStopping || params.abortSignal?.aborted === true)) + ) { return "stop"; } throw event.err;