Bug Description
handleGatewayReaction and handleForwardedReaction in the Discord adapter produce channel-level thread IDs for reactions on messages inside threads. The threadId on ReactionEvent is a 3-segment ID (discord:{guildId}:{channelId}) even when the reacted message is in a thread, where it should be 4-segment (discord:{guildId}:{parentChannelId}:{threadId}).
Steps to Reproduce
- Register a reaction handler:
chat.onReaction(["x"], (event) => { console.log(event.threadId); })
- Send a message that triggers the bot to reply in a Discord thread
- React with ❌ on a message inside that thread
- Observe
event.threadId — it's discord:{guildId}:{threadChannelId} (3 segments) instead of discord:{guildId}:{parentChannelId}:{threadChannelId} (4 segments)
Expected Behavior
event.threadId should be the full 4-segment ID (discord:{guildId}:{parentChannelId}:{threadId}) when the reaction is on a message inside a thread, matching the format produced by handleGatewayMessage.
Actual Behavior
event.threadId is a 3-segment channel-level ID (discord:{guildId}:{channelId}) — the thread snowflake is placed in the channel slot and the parent channel is lost. This means event.threadId from onReaction doesn't match the threadId used by onNewMention/onSubscribedMessage for the same thread.
Code Sample
// handleGatewayReaction currently (packages/adapter-discord/src/index.ts ~L1664):
const threadId = this.encodeThreadId({ guildId, channelId, threadId: undefined });
// Should resolve thread context like handleGatewayMessage does (~L1555):
const isInThread = reaction.message.channel?.isThread?.();
let parentChannelId = channelId;
let discordThreadId;
if (isInThread && "parentId" in reaction.message.channel && reaction.message.channel.parentId) {
discordThreadId = channelId;
parentChannelId = reaction.message.channel.parentId;
}
const threadId = this.encodeThreadId({ guildId, channelId: parentChannelId, threadId: discordThreadId });
Chat SDK Version
4.15.0
Node.js Version
No response
Platform Adapter
Discord
Operating System
macOS
Additional Context
Both handleGatewayReaction (~L1664) and handleForwardedReaction (~L547) are affected. The fix pattern already
Note: handleGatewayReaction's type signature narrows reaction.message to { id: string; channelId: string; guildId: string | null }, which doesn't include channel. But at runtime, the caller passes the full discord.js MessageReaction object, so reaction.message.channel (with .isThread() and .parentId) is available. The type signature needs to be widened to include channel for the fix to typecheck.exists in handleGatewayMessage (~L1555) which uses message.channel.isThread() and channel.parentId to resolve the correct thread context.
I (claude) patched this locally and it worked as expected
Bug Description
handleGatewayReactionandhandleForwardedReactionin the Discord adapter produce channel-level thread IDs for reactions on messages inside threads. ThethreadIdonReactionEventis a 3-segment ID (discord:{guildId}:{channelId}) even when the reacted message is in a thread, where it should be 4-segment (discord:{guildId}:{parentChannelId}:{threadId}).Steps to Reproduce
chat.onReaction(["x"], (event) => { console.log(event.threadId); })event.threadId— it'sdiscord:{guildId}:{threadChannelId}(3 segments) instead ofdiscord:{guildId}:{parentChannelId}:{threadChannelId}(4 segments)Expected Behavior
event.threadIdshould be the full 4-segment ID (discord:{guildId}:{parentChannelId}:{threadId}) when the reaction is on a message inside a thread, matching the format produced byhandleGatewayMessage.Actual Behavior
event.threadIdis a 3-segment channel-level ID (discord:{guildId}:{channelId}) — the thread snowflake is placed in the channel slot and the parent channel is lost. This meansevent.threadIdfromonReactiondoesn't match thethreadIdused byonNewMention/onSubscribedMessagefor the same thread.Code Sample
Chat SDK Version
4.15.0
Node.js Version
No response
Platform Adapter
Discord
Operating System
macOS
Additional Context
Both
handleGatewayReaction(~L1664) andhandleForwardedReaction(~L547) are affected. The fix pattern alreadyNote:
handleGatewayReaction's type signature narrowsreaction.messageto{ id: string; channelId: string; guildId: string | null }, which doesn't includechannel. But at runtime, the caller passes the full discord.jsMessageReactionobject, soreaction.message.channel(with.isThread()and.parentId) is available. The type signature needs to be widened to includechannelfor the fix to typecheck.exists inhandleGatewayMessage(~L1555) which usesmessage.channel.isThread()andchannel.parentIdto resolve the correct thread context.I (claude) patched this locally and it worked as expected