From e97f484ef92e5a73f50842275a42d595b02497c7 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 3 Aug 2026 02:13:42 -0700 Subject: [PATCH] fix(discord): report pending message search indexes (#118573) Co-authored-by: Peter Steinberger --- extensions/discord/src/send.messages.test.ts | 32 ++++++++++++++++++++ extensions/discord/src/send.messages.ts | 17 ++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/extensions/discord/src/send.messages.test.ts b/extensions/discord/src/send.messages.test.ts index 2dfaa119b6b2..d5fb287ec012 100644 --- a/extensions/discord/src/send.messages.test.ts +++ b/extensions/discord/src/send.messages.test.ts @@ -66,6 +66,38 @@ describe("searchMessagesDiscord", () => { expect(result).toEqual(results); }); + it("preserves valid empty Discord search results", async () => { + const results = { messages: [], total_results: 0 }; + restMock.get.mockResolvedValueOnce(results); + + await expect( + searchMessagesDiscord({ guildId: "G1", content: "test" }, { cfg: {} as never }), + ).resolves.toEqual(results); + }); + + it("surfaces a pending Discord search index and its retry delay", async () => { + restMock.get.mockResolvedValueOnce({ + message: "Index not yet available. Try again later", + code: 110000, + documents_indexed: 0, + retry_after: 2, + }); + + await expect( + searchMessagesDiscord({ guildId: "G1", content: "test" }, { cfg: {} as never }), + ).rejects.toThrow( + "Discord message search unavailable: Index not yet available. Try again later (retry after 2s)", + ); + }); + + it("rejects object search responses without a messages array", async () => { + restMock.get.mockResolvedValueOnce({ total_results: 1 }); + + await expect( + searchMessagesDiscord({ guildId: "G1", content: "test" }, { cfg: {} as never }), + ).rejects.toThrow("Unexpected Discord response for message search: expected messages array."); + }); + it("throws a clear error when Discord returns a non-object search response", async () => { restMock.get.mockResolvedValueOnce("\u001f\ufffd\u0008raw gzip bytes"); diff --git a/extensions/discord/src/send.messages.ts b/extensions/discord/src/send.messages.ts index 9148c7087ccb..63699b2708f7 100644 --- a/extensions/discord/src/send.messages.ts +++ b/extensions/discord/src/send.messages.ts @@ -17,6 +17,7 @@ import { searchGuildMessages, unpinChannelMessage, } from "./internal/discord.js"; +import { parseDiscordRetryAfterBodySeconds } from "./retry-after.js"; import { resolveDiscordRest } from "./send.shared.js"; import type { DiscordMessageEdit, @@ -248,8 +249,22 @@ export async function searchMessagesDiscord(query: DiscordSearchQuery, opts: Dis const limit = Math.min(Math.max(Math.floor(query.limit), 1), 25); params.set("limit", String(limit)); } - return assertDiscordResponseObject( + const result = assertDiscordResponseObject( await searchGuildMessages(rest, query.guildId, params), "message search", ); + // Discord returns HTTP 202 with code 110000 while the guild search index is warming. + if (result.code === 110000) { + const message = + typeof result.message === "string" && result.message.trim() + ? result.message.trim() + : "Discord search index is not yet available"; + const retryAfter = parseDiscordRetryAfterBodySeconds(result.retry_after); + const retryHint = retryAfter === undefined ? "" : ` (retry after ${retryAfter}s)`; + throw new Error(`Discord message search unavailable: ${message}${retryHint}`); + } + if (!Array.isArray(result.messages)) { + throw new Error("Unexpected Discord response for message search: expected messages array."); + } + return result; }