fix(discord): requireMention regression β guild messages misclassified as DMs#34879
fix(discord): requireMention regression β guild messages misclassified as DMs#34879leo919cc wants to merge 2 commits intoopenclaw:mainfrom
Conversation
β¦d as DMs Two independent bugs caused requireMention to be silently ignored for guild messages (openclaw#34353): 1. isDirectMessage was derived solely from channelInfo.type (fetched via API/cache), which can report ChannelType.DM for guild channels due to stale cache or Carbon library inconsistencies. When isDirectMessage was true, mention detection was forced false and the message bypassed the mention gate. Fix: gate isDirectMessage (and isGroupDm) on !isGuildMessage so guild_id from the event payload takes precedence. 2. The mention-gate drop was guarded by `if (botId && ...)`, which silently disabled the gate when botId was unavailable. Mention detection can still work via mentionPatterns regexes, so the botId guard was overly restrictive. Fix: remove the botId guard; the existing canDetectMention check inside resolveMentionGatingWithBypass already handles the "no detection available" case. Adds two regression tests covering both paths. Fixes openclaw#34353
Greptile SummaryThis PR fixes two independent regressions in Fix 1 β Stale Fix 2 β Redundant Minor observation: Confidence Score: 4/5
Last reviewed commit: 2e674d5 |
| }); | ||
|
|
||
| it("does not classify guild message as DM when channelInfo.type is stale DM (fixes #34353)", async () => { | ||
| const channelId = "channel-stale-dm"; | ||
| const guildId = "guild-stale-dm"; | ||
| const client = { | ||
| fetchChannel: async (id: string) => { | ||
| if (id === channelId) { | ||
| // Stale or inconsistent channel info: reports DM even though the | ||
| // message carries a guild_id. | ||
| return { | ||
| id: channelId, | ||
| type: ChannelType.DM, | ||
| name: null, | ||
| }; | ||
| } | ||
| return null; | ||
| }, | ||
| } as unknown as import("@buape/carbon").Client; | ||
| const message = { | ||
| id: "m-stale-dm-1", | ||
| content: "hello <@openclaw-bot>", | ||
| timestamp: new Date().toISOString(), | ||
| channelId, | ||
| attachments: [], | ||
| mentionedUsers: [{ id: "openclaw-bot" }], | ||
| mentionedRoles: [], | ||
| mentionedEveryone: false, | ||
| author: { | ||
| id: "user-1", | ||
| bot: false, | ||
| username: "Alice", | ||
| }, | ||
| } as unknown as import("@buape/carbon").Message; | ||
|
|
||
| const result = await preflightDiscordMessage({ | ||
| cfg: { | ||
| session: { | ||
| mainKey: "main", | ||
| scope: "per-sender", | ||
| }, | ||
| } as import("../../config/config.js").OpenClawConfig, | ||
| discordConfig: {} as NonNullable< | ||
| import("../../config/config.js").OpenClawConfig["channels"] | ||
| >["discord"], | ||
| accountId: "default", | ||
| token: "token", | ||
| runtime: {} as import("../../runtime.js").RuntimeEnv, | ||
| botUserId: "openclaw-bot", | ||
| guildHistories: new Map(), | ||
| historyLimit: 0, | ||
| mediaMaxBytes: 1_000_000, | ||
| textLimit: 2_000, | ||
| replyToMode: "all", | ||
| dmEnabled: true, | ||
| groupDmEnabled: true, | ||
| ackReactionScope: "direct", | ||
| groupPolicy: "open", | ||
| threadBindings: createNoopThreadBindingManager("default"), | ||
| guildEntries: { | ||
| [guildId]: { | ||
| requireMention: true, | ||
| }, | ||
| }, | ||
| data: { | ||
| channel_id: channelId, | ||
| guild_id: guildId, | ||
| guild: { | ||
| id: guildId, | ||
| name: "Guild Stale DM", | ||
| }, | ||
| author: message.author, | ||
| message, | ||
| } as unknown as import("./listeners.js").DiscordMessageEvent, | ||
| client, | ||
| }); | ||
|
|
||
| // Before the fix, isDirectMessage would be true (stale channelInfo) which |
There was a problem hiding this comment.
Missing inverse regression for Fix #1 (no-mention + stale DM channelInfo)
The first new test verifies that a guild message with a mention still passes when channelInfo.type is stale DM β which is a good "don't over-drop" check. However, the primary regression from #34353 is the opposite path: a guild message without a mention that was incorrectly allowed through because isDirectMessage=true bypassed the requireMention gate entirely.
Consider adding a complementary assertion (or a separate it block) where mentionedUsers: [] and the content contains no mention pattern, and expecting result to be null. This would directly pin down the root regression rather than relying on the positive path alone.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/discord/monitor/message-handler.preflight.test.ts
Line: 729-806
Comment:
**Missing inverse regression for Fix #1 (no-mention + stale DM channelInfo)**
The first new test verifies that a guild message *with* a mention still passes when `channelInfo.type` is stale DM β which is a good "don't over-drop" check. However, the primary regression from #34353 is the opposite path: a guild message *without* a mention that was incorrectly allowed through because `isDirectMessage=true` bypassed the `requireMention` gate entirely.
Consider adding a complementary assertion (or a separate `it` block) where `mentionedUsers: []` and the content contains no mention pattern, and expecting `result` to be `null`. This would directly pin down the root regression rather than relying on the positive path alone.
How can I resolve this? If you propose a fix, please make it concise.Addresses review comment: adds test that verifies guild messages WITHOUT a mention are correctly dropped when channelInfo has a stale DM type β the actual regression path for openclaw#34353. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
requireMention: truewere not being gated β the agent responded to every message regardless of mention ([Bug]: Discord requireMention not working - agent responds and shows typing even without mentionΒ #34353). Two independent root causes:isDirectMessagewas derived solely fromchannelInfo.type(API/cache), which can returnChannelType.DMfor guild channels due to stale cache or Carbon library inconsistencies. This forcedwasMentioned=falseand bypassed the mention gate.if (botId && mentionGate.shouldSkip), silently disabling the gate when the bot's Discord user ID was unavailable β even though mention detection still works viamentionPatternsregexes.requireMention: trueexpect the agent to stay silent in guild channels unless explicitly mentioned. Both bugs independently defeat this.src/discord/monitor/message-handler.preflight.tsβ two targeted fixes + structured log fields for the drop path. Two new regression tests.mention-gating.ts), allowlist resolution, or any other channel handler.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
User-visible / Behavior Changes
requireMention: trueare now correctly dropped when the bot is not mentioned (restoring v2026.3.1 behavior).chat_type: "direct"whenchannelInfois stale.Security Impact (required)
Repro + Verification
Environment
discord.guilds.<id>.requireMention: trueSteps
requireMention: trueExpected
Actual (before fix)
Evidence
Two new test cases added:
does not classify guild message as DM when channelInfo.type is stale DMβ verifies fix fix: add @lid format support and allowFrom wildcard handlingΒ #1drops guild message without mention even when botUserId is absentβ verifies fix Login fails with 'WebSocket Error (socket hang up)' ECONNRESETΒ #2Full test suite: 6587/6587 passed. Build and lint clean.
Human Verification (required)
guild_idβisDirectMessageβwasMentionedβmentionGateβ drop/pass decision.Compatibility / Migration
Failure Recovery (if this breaks)
message-handler.preflight.ts.!isGuildMessageguard is wrong), or guild messages still bypassing mention gate.Risks and Mitigations
guild_idis spuriously set.guild_idper the Discord API contract. Theguild_idfield is only present on guild message events.AI Disclosure
pnpm build && pnpm check && pnpm test(6587/6587 pass)