From 857424bbaeb8cb2981e703f932157ec6df0036bd Mon Sep 17 00:00:00 2001 From: zerone0x Date: Mon, 16 Feb 2026 15:23:51 +0100 Subject: [PATCH] fix(discord): send initial message for non-forum thread creation When creating a thread in a non-forum channel with the `content` parameter, the content was silently ignored. This happened because only forum/media channels support the `message` field in the thread creation request body. This fix sends the initial message as a separate API call after the thread is created, ensuring the content parameter works for all thread types. Fixes the issue where `thread-create` with `message` parameter appeared to succeed but the message never appeared in the thread. --- src/discord/send.creates-thread.test.ts | 55 +++++++++++++++++++++++++ src/discord/send.messages.ts | 12 +++++- 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/src/discord/send.creates-thread.test.ts b/src/discord/send.creates-thread.test.ts index 5e2f5b2d731..957b709937b 100644 --- a/src/discord/send.creates-thread.test.ts +++ b/src/discord/send.creates-thread.test.ts @@ -107,6 +107,61 @@ describe("sendMessageDiscord", () => { ); }); + it("sends initial message for non-forum threads with content", async () => { + const { rest, getMock, postMock } = makeDiscordRest(); + getMock.mockResolvedValue({ type: ChannelType.GuildText }); + postMock.mockResolvedValue({ id: "t1" }); + await createThreadDiscord( + "chan1", + { name: "thread", content: "Hello thread!" }, + { rest, token: "t" }, + ); + expect(postMock).toHaveBeenCalledTimes(2); + // First call: create thread + expect(postMock).toHaveBeenNthCalledWith( + 1, + Routes.threads("chan1"), + expect.objectContaining({ + body: expect.objectContaining({ name: "thread", type: ChannelType.PublicThread }), + }), + ); + // Second call: send message to thread + expect(postMock).toHaveBeenNthCalledWith( + 2, + Routes.channelMessages("t1"), + expect.objectContaining({ + body: { content: "Hello thread!" }, + }), + ); + }); + + it("sends initial message for message-attached threads with content", async () => { + const { rest, getMock, postMock } = makeDiscordRest(); + postMock.mockResolvedValue({ id: "t1" }); + await createThreadDiscord( + "chan1", + { name: "thread", messageId: "m1", content: "Discussion here" }, + { rest, token: "t" }, + ); + // Should not detect channel type for message-attached threads + expect(getMock).not.toHaveBeenCalled(); + expect(postMock).toHaveBeenCalledTimes(2); + // First call: create thread from message + expect(postMock).toHaveBeenNthCalledWith( + 1, + Routes.threads("chan1", "m1"), + expect.objectContaining({ body: { name: "thread" } }), + ); + // Second call: send message to thread + expect(postMock).toHaveBeenNthCalledWith( + 2, + Routes.channelMessages("t1"), + expect.objectContaining({ + body: { content: "Discussion here" }, + }), + ); + }); + it("lists active threads by guild", async () => { const { rest, getMock } = makeDiscordRest(); getMock.mockResolvedValue({ threads: [] }); diff --git a/src/discord/send.messages.ts b/src/discord/send.messages.ts index 92ff6bb8ebb..1c8c67499a1 100644 --- a/src/discord/send.messages.ts +++ b/src/discord/send.messages.ts @@ -134,7 +134,17 @@ export async function createThreadDiscord( const route = payload.messageId ? Routes.threads(channelId, payload.messageId) : Routes.threads(channelId); - return await rest.post(route, { body }); + const thread = (await rest.post(route, { body })) as { id: string }; + + // For non-forum channels, send the initial message separately after thread creation. + // Forum channels handle this via the `message` field in the request body. + if (!isForumLike && payload.content?.trim()) { + await rest.post(Routes.channelMessages(thread.id), { + body: { content: payload.content }, + }); + } + + return thread; } export async function listThreadsDiscord(payload: DiscordThreadList, opts: DiscordReactOpts = {}) {