diff --git a/extensions/discord/src/monitor/message-handler.preflight.ts b/extensions/discord/src/monitor/message-handler.preflight.ts index 9094cabb645..56b4569a118 100644 --- a/extensions/discord/src/monitor/message-handler.preflight.ts +++ b/extensions/discord/src/monitor/message-handler.preflight.ts @@ -788,7 +788,7 @@ export async function preflightDiscordMessage( `[discord-preflight] shouldRequireMention=${shouldRequireMention} baseRequireMention=${shouldRequireMentionByConfig} boundThreadSession=${isBoundThreadSession} mentionGate.shouldSkip=${mentionGate.shouldSkip} wasMentioned=${wasMentioned}`, ); if (isGuildMessage && shouldRequireMention) { - if (botId && mentionGate.shouldSkip) { + if (mentionGate.shouldSkip) { logDebug(`[discord-preflight] drop: no-mention`); logVerbose(`discord: drop guild message (mention required, botId=${botId})`); logger.info( diff --git a/extensions/discord/src/monitor/provider.test.ts b/extensions/discord/src/monitor/provider.test.ts index ff6fb310464..c43cbee1fd7 100644 --- a/extensions/discord/src/monitor/provider.test.ts +++ b/extensions/discord/src/monitor/provider.test.ts @@ -627,4 +627,32 @@ describe("monitorDiscordProvider", () => { const messages = vi.mocked(runtime.log).mock.calls.map((call) => String(call[0])); expect(messages.some((msg) => msg.includes("discord startup ["))).toBe(false); }); + + it("throws when fetchUser('@me') fails to prevent running without bot identity (#42219)", async () => { + const { monitorDiscordProvider } = await import("./provider.js"); + const runtime = baseRuntime(); + + clientFetchUserMock.mockRejectedValueOnce(new Error("network timeout")); + + await expect( + monitorDiscordProvider({ + config: baseConfig(), + runtime, + }), + ).rejects.toThrow("discord: cannot start without bot identity"); + }); + + it("throws when fetchUser('@me') returns no user id", async () => { + const { monitorDiscordProvider } = await import("./provider.js"); + const runtime = baseRuntime(); + + clientFetchUserMock.mockResolvedValueOnce({ id: undefined, username: "NoId" }); + + await expect( + monitorDiscordProvider({ + config: baseConfig(), + runtime, + }), + ).rejects.toThrow("discord: fetchUser('@me') returned no user id"); + }); }); diff --git a/extensions/discord/src/monitor/provider.ts b/extensions/discord/src/monitor/provider.ts index 8dbb6df29f5..aa38bf6cfc3 100644 --- a/extensions/discord/src/monitor/provider.ts +++ b/extensions/discord/src/monitor/provider.ts @@ -880,6 +880,15 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) { gateway: lifecycleGateway, details: String(err), }); + // Fail-fast: without botUserId, mention gating is bypassed (all guild + // messages pass through), self-message filtering is disabled (risk of + // self-reply loops), and reply detection is broken. Let auto-restart + // retry instead of running in a degraded state. See #42219. + throw new Error(`discord: cannot start without bot identity: ${String(err)}`); + } + if (!botUserId) { + // fetchUser succeeded but returned no id — equally unsafe to continue. + throw new Error("discord: fetchUser('@me') returned no user id"); } if (voiceEnabled) {