Description
strip_mention in src/discord.rs uses a blanket regex to remove all Discord mention tokens from the prompt before sending to the agent. The original intent was only to strip the bot's own trigger mention, but it inadvertently erases every other <@ID> in the message — including references to other bots or users.
// src/discord.rs
static MENTION_RE: LazyLock<regex::Regex> = LazyLock::new(|| {
regex::Regex::new(r"<@[!&]?\d+>").unwrap()
});
fn strip_mention(content: &str) -> String {
MENTION_RE.replace_all(content, "").trim().to_string() // strips ALL mentions
}
// called at:
let prompt = if is_mentioned {
strip_mention(&msg.content) // <-- nukes every <@ID> in the message
} else {
msg.content.trim().to_string()
};
This breaks bot-to-bot collaboration introduced in v0.7.4 (allowBotMessages): when bot A @mentions bot B in a message to bot C, bot C's prompt arrives with the @mention of bot B silently removed.
Steps to Reproduce
- Deploy two bots (e.g. 超渡法師, 普渡法師) with
allowBotMessages: mentions and mutual trustedBotIds
- In a shared channel, send:
@超渡法師 you can see @普渡法師 right?
- Observe the prompt received by 超渡法師's agent
Expected Behavior
The agent receives: you can see @普渡法師 right?
The bot's own trigger mention is stripped, but all other <@ID> tokens are resolved to human-readable @DisplayName using msg.mentions.
Actual Behavior
The agent receives: you can see right?
All mentions are stripped, including @普渡法師.
Before / After
BEFORE (strip_mention)
──────────────────────────────────────────────────────────
Discord message: "<@超渡ID> you can see <@普渡ID> right?"
│
strip_mention()
regex: <@[!&]?\d+> ← matches ALL
│
▼
Agent prompt: "you can see right?"
↑
@普渡法師 erased ❌
AFTER (resolve_mentions)
──────────────────────────────────────────────────────────
Discord message: "<@超渡ID> you can see <@普渡ID> right?"
│
resolve_mentions(bot_id, msg.mentions)
step 1: remove own ID → strip <@超渡ID>
step 2: resolve others → <@普渡ID> → @普渡法師
│
▼
Agent prompt: "you can see @普渡法師 right?" ✅
Insights from OpenClaw
OpenClaw solves this correctly in src/discord/monitor/message-utils.ts.
Instead of stripping, it resolves <@ID> tokens to human-readable @DisplayName using the message's mentionedUsers list:
// src/discord/monitor/message-utils.ts
function resolveDiscordMentions(text: string, message: Message): string {
if (!text.includes('<')) return text;
const mentions = message.mentionedUsers ?? [];
if (!Array.isArray(mentions) || mentions.length === 0) return text;
let out = text;
for (const user of mentions) {
const label = user.globalName || user.username;
out = out.replace(new RegExp(`<@!?${user.id}>`, 'g'), `@${label}`);
}
return out;
}
This is called from resolveDiscordMessageText — the single entry point that normalises all inbound message content before it reaches the agent. The key insight: never strip, always resolve.
OpenClaw also has a companion rewriteDiscordKnownMentions that converts plain-text @handle back to <@ID> on the outbound side, completing the round-trip. openab only needs the inbound half.
Suggested Fix
Replace strip_mention with resolve_mentions that:
- Removes only the bot's own trigger mention (
<@BOT_ID>)
- Replaces all other
<@ID> tokens with @display_name from msg.mentions
fn resolve_mentions(content: &str, bot_id: UserId, mentions: &[User]) -> String {
let mut out = Regex::new(&format!(r"<@!?{}>", bot_id))
.unwrap()
.replace_all(content, "")
.to_string();
for user in mentions {
let label = user.global_name.as_deref().unwrap_or(&user.name);
let re = Regex::new(&format!(r"<@!?{}>", user.id)).unwrap();
out = re.replace_all(&out, format!("@{}", label)).to_string();
}
out.trim().to_string()
}
Call site change in message_handler:
// Before
let prompt = strip_mention(&msg.content);
// After
let prompt = resolve_mentions(&msg.content, bot_id, &msg.mentions);
DC: https://discord.com/channels/1491295327620169908/1494031440482926706
Description
strip_mentioninsrc/discord.rsuses a blanket regex to remove all Discord mention tokens from the prompt before sending to the agent. The original intent was only to strip the bot's own trigger mention, but it inadvertently erases every other<@ID>in the message — including references to other bots or users.This breaks bot-to-bot collaboration introduced in v0.7.4 (
allowBotMessages): when bot A @mentions bot B in a message to bot C, bot C's prompt arrives with the @mention of bot B silently removed.Steps to Reproduce
allowBotMessages: mentionsand mutualtrustedBotIds@超渡法師 you can see @普渡法師 right?Expected Behavior
The agent receives:
you can see @普渡法師 right?The bot's own trigger mention is stripped, but all other
<@ID>tokens are resolved to human-readable@DisplayNameusingmsg.mentions.Actual Behavior
The agent receives:
you can see right?All mentions are stripped, including
@普渡法師.Before / After
Insights from OpenClaw
OpenClaw solves this correctly in
src/discord/monitor/message-utils.ts.Instead of stripping, it resolves
<@ID>tokens to human-readable@DisplayNameusing the message'smentionedUserslist:This is called from
resolveDiscordMessageText— the single entry point that normalises all inbound message content before it reaches the agent. The key insight: never strip, always resolve.OpenClaw also has a companion
rewriteDiscordKnownMentionsthat converts plain-text@handleback to<@ID>on the outbound side, completing the round-trip. openab only needs the inbound half.Suggested Fix
Replace
strip_mentionwithresolve_mentionsthat:<@BOT_ID>)<@ID>tokens with@display_namefrommsg.mentionsCall site change in
message_handler:DC: https://discord.com/channels/1491295327620169908/1494031440482926706