diff --git a/CHANGELOG.md b/CHANGELOG.md index 913198b9d1..2444d4cf74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Docs: https://docs.openclaw.ai - Ollama: use configured `models.providers.ollama.baseUrl` for model discovery and normalize `/v1` endpoints to the native Ollama API root. (#14131) Thanks @shtse8. - Slack: detect control commands when channel messages start with bot mention prefixes (for example, `@Bot /new`). (#14142) Thanks @beefiker. +- Discord: default standalone threads to public while honoring explicit thread type overrides. (#14162) Thanks @0xRaini. ## 2026.2.9 diff --git a/src/discord/send.creates-thread.test.ts b/src/discord/send.creates-thread.test.ts index 3b332c06bc..8b5994f4c7 100644 --- a/src/discord/send.creates-thread.test.ts +++ b/src/discord/send.creates-thread.test.ts @@ -114,7 +114,27 @@ describe("sendMessageDiscord", () => { await createThreadDiscord("chan1", { name: "thread" }, { rest, token: "t" }); expect(postMock).toHaveBeenCalledWith( Routes.threads("chan1"), - expect.objectContaining({ body: { name: "thread" } }), + expect.objectContaining({ + body: expect.objectContaining({ name: "thread", type: ChannelType.PublicThread }), + }), + ); + }); + + it("respects explicit thread type for standalone threads", async () => { + const { rest, getMock, postMock } = makeRest(); + getMock.mockResolvedValue({ type: ChannelType.GuildText }); + postMock.mockResolvedValue({ id: "t1" }); + await createThreadDiscord( + "chan1", + { name: "thread", type: ChannelType.PrivateThread }, + { rest, token: "t" }, + ); + expect(getMock).toHaveBeenCalledWith(Routes.channel("chan1")); + expect(postMock).toHaveBeenCalledWith( + Routes.threads("chan1"), + expect.objectContaining({ + body: expect.objectContaining({ name: "thread", type: ChannelType.PrivateThread }), + }), ); }); diff --git a/src/discord/send.messages.ts b/src/discord/send.messages.ts index bd8bcf2bb1..92ff6bb8eb 100644 --- a/src/discord/send.messages.ts +++ b/src/discord/send.messages.ts @@ -105,6 +105,9 @@ export async function createThreadDiscord( if (payload.autoArchiveMinutes) { body.auto_archive_duration = payload.autoArchiveMinutes; } + if (!payload.messageId && payload.type !== undefined) { + body.type = payload.type; + } let channelType: ChannelType | undefined; if (!payload.messageId) { // Only detect channel kind for route-less thread creation. @@ -122,6 +125,12 @@ export async function createThreadDiscord( const starterContent = payload.content?.trim() ? payload.content : payload.name; body.message = { content: starterContent }; } + // When creating a standalone thread (no messageId) in a non-forum channel, + // default to public thread (type 11). Discord defaults to private (type 12) + // which is unexpected for most users. (#14147) + if (!payload.messageId && !isForumLike && body.type === undefined) { + body.type = ChannelType.PublicThread; + } const route = payload.messageId ? Routes.threads(channelId, payload.messageId) : Routes.threads(channelId); diff --git a/src/discord/send.types.ts b/src/discord/send.types.ts index 5a171a7566..318a03002e 100644 --- a/src/discord/send.types.ts +++ b/src/discord/send.types.ts @@ -72,6 +72,8 @@ export type DiscordThreadCreate = { name: string; autoArchiveMinutes?: number; content?: string; + /** Discord thread type (default: PublicThread for standalone threads). */ + type?: number; }; export type DiscordThreadList = {