Merge a87796d596178b271082c1d8743ef4dfdb95707e into 5e417b44e1540f528d2ae63e3e20229a902d1db2

This commit is contained in:
Benedikt Schackenberg 2026-03-21 05:00:17 +03:00 committed by GitHub
commit 6a1a273ac2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 47 additions and 1 deletions

View File

@ -791,7 +791,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(

View File

@ -627,4 +627,36 @@ 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("falls back to applicationId when fetchUser('@me') fails (#42219)", async () => {
const { monitorDiscordProvider } = await import("./provider.js");
const runtime = baseRuntime();
clientFetchUserMock.mockRejectedValueOnce(new Error("network timeout"));
// Should NOT throw — the code now falls back to applicationId as botUserId
// instead of aborting. We race with a short timeout to confirm no immediate rejection.
const result = await Promise.race([
monitorDiscordProvider({
config: baseConfig(),
runtime,
}).then(() => "resolved", (err: Error) => `rejected: ${err.message}`),
new Promise<string>((r) => setTimeout(() => r("still-running"), 200)),
]);
expect(result).toBe("still-running");
});
it("throws when fetchUser('@me') returns no user id", async () => {
const { monitorDiscordProvider } = await import("./provider.js");
const runtime = baseRuntime();
clientFetchUserMock.mockResolvedValueOnce({ id: undefined as unknown as string, username: "NoId" });
await expect(
monitorDiscordProvider({
config: baseConfig(),
runtime,
}),
).rejects.toThrow("discord: fetchUser('@me') returned no user id");
});
});

View File

@ -882,6 +882,20 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
gateway: lifecycleGateway,
details: String(err),
});
// Transient REST/proxy failures should not take down the whole channel.
// applicationId is the same snowflake as the bot user id and was already
// resolved above (with a token-decoding fallback), so we can use it for
// mention gating and self-message filtering. botUserName is only used for
// logging and is non-critical.
botUserId = applicationId;
runtime.warn?.(
`discord: using applicationId as botUserId fallback (fetchUser failed: ${String(err)})`,
);
}
if (!botUserId) {
// fetchUser succeeded but returned no id and applicationId is also missing —
// this should not happen in practice.
throw new Error("discord: fetchUser('@me') returned no user id");
}
if (voiceEnabled) {