Skip to content

fix(discord): requireMention regression β€” guild messages misclassified as DMs#34879

Open
leo919cc wants to merge 2 commits intoopenclaw:mainfrom
leo919cc:fix/discord-require-mention-regression
Open

fix(discord): requireMention regression β€” guild messages misclassified as DMs#34879
leo919cc wants to merge 2 commits intoopenclaw:mainfrom
leo919cc:fix/discord-require-mention-regression

Conversation

@leo919cc
Copy link

@leo919cc leo919cc commented Mar 4, 2026

Summary

  • Problem: Discord guild messages with requireMention: true were 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:
    1. isDirectMessage was derived solely from channelInfo.type (API/cache), which can return ChannelType.DM for guild channels due to stale cache or Carbon library inconsistencies. This forced wasMentioned=false and bypassed the mention gate.
    2. The mention-gate drop was guarded by if (botId && mentionGate.shouldSkip), silently disabling the gate when the bot's Discord user ID was unavailable β€” even though mention detection still works via mentionPatterns regexes.
  • Why it matters: Users configuring requireMention: true expect the agent to stay silent in guild channels unless explicitly mentioned. Both bugs independently defeat this.
  • What changed: src/discord/monitor/message-handler.preflight.ts β€” two targeted fixes + structured log fields for the drop path. Two new regression tests.
  • What did NOT change: No changes to mention-gating logic (mention-gating.ts), allowlist resolution, or any other channel handler.

Change Type (select all)

  • Bug fix

Scope (select all touched areas)

  • Integrations

Linked Issue/PR

User-visible / Behavior Changes

  • Guild messages in servers with requireMention: true are now correctly dropped when the bot is not mentioned (restoring v2026.3.1 behavior).
  • Guild messages are no longer misclassified as chat_type: "direct" when channelInfo is stale.

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: Any
  • Runtime/container: Node 22+
  • Integration/channel: Discord
  • Relevant config: discord.guilds.<id>.requireMention: true

Steps

  1. Configure a Discord guild with requireMention: true
  2. Send a message in a guild channel WITHOUT mentioning the bot
  3. Observe whether the message is processed or dropped

Expected

  • Message is dropped (agent stays silent)

Actual (before fix)

  • Message is processed and agent responds

Evidence

  • Failing test/log before + passing after

Two new test cases added:

Full test suite: 6587/6587 passed. Build and lint clean.

Human Verification (required)

  • Verified scenarios: Both regression tests exercise the exact code paths described in the bug. Traced the full preflight flow from guild_id β†’ isDirectMessage β†’ wasMentioned β†’ mentionGate β†’ drop/pass decision.
  • Edge cases checked: Stale DM channelInfo with guild_id present; absent botUserId with mentionPatterns regex available; normal guild messages with/without mentions.
  • What I did not verify: Live Discord bot testing (no Discord bot available in dev).

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: Revert the single commit on message-handler.preflight.ts.
  • Known bad symptoms reviewers should watch for: DMs being dropped (if !isGuildMessage guard is wrong), or guild messages still bypassing mention gate.

Risks and Mitigations

  • Risk: Real DMs might be misclassified if guild_id is spuriously set.
    • Mitigation: Discord DM events never carry guild_id per the Discord API contract. The guild_id field is only present on guild message events.

AI Disclosure

  • AI-assisted (Claude Code)
  • Degree of testing: Fully tested β€” pnpm build && pnpm check && pnpm test (6587/6587 pass)
  • Code was reviewed by Codex (GPT-5.1) via codereview tool β€” zero issues found
  • I understand what the code does: root cause traced through the full preflight flow across 5 source files

…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
@openclaw-barnacle openclaw-barnacle bot added channel: discord Channel integration: discord size: S labels Mar 4, 2026
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 4, 2026

Greptile Summary

This PR fixes two independent regressions in message-handler.preflight.ts that caused Discord guild messages to bypass the requireMention: true gate (restoring behavior from v2026.3.1). Both fixes are minimal, well-targeted, and well-explained.

Fix 1 β€” Stale channelInfo.type misclassification:
isDirectMessage and isGroupDm are now derived as !isGuildMessage && channelInfo?.type === ChannelType.* instead of relying solely on channelInfo.type. Since Discord's API contract guarantees that guild message events always carry guild_id and DM events never do, this is the correct authoritative signal. The fix is safe.

Fix 2 β€” Redundant botId && gate removed:
The old guard if (botId && mentionGate.shouldSkip) silently disabled the mention gate when botId was absent, even when mentionPatterns regexes were available for mention detection. Since canDetectMention (already used inside resolveMentionGatingWithBypass) already accounts for regex-based detection as a fallback (Boolean(botId) || mentionRegexes.length > 0), the botId && pre-check was redundant and harmful. The new guard if (mentionGate.shouldSkip) is both simpler and correct.

Minor observation:
The first regression test covers the "mentioned + stale channelInfo β†’ passes" path. The more critical inverse path β€” "not mentioned + stale channelInfo β†’ dropped (null)" β€” is not directly asserted and would strengthen the test coverage for the exact regression scenario described in #34353.

Confidence Score: 4/5

  • This PR is safe to merge β€” the two targeted fixes correctly address the root causes without introducing regressions.
  • Both fixes are logically sound and well-reasoned: (1) guild_id is the correct authoritative signal per Discord's API contract, making the !isGuildMessage guard safe; (2) removing botId && is correct because canDetectMention inside mentionGate.shouldSkip already handles the botId-absent case via mentionRegexes. The only gap is a missing inverse assertion in the first regression test, but this does not affect correctness of the production fix.
  • No files require special attention beyond the minor test-coverage gap noted in src/discord/monitor/message-handler.preflight.test.ts.

Last reviewed commit: 2e674d5

Comment on lines 729 to +806
});

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: discord Channel integration: discord size: M

Projects

None yet

1 participant