Skip to content

bug: strip_mention removes all mentions from prompt, breaking bot-to-bot @mention visibility #363

@thepagent

Description

@thepagent

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

  1. Deploy two bots (e.g. 超渡法師, 普渡法師) with allowBotMessages: mentions and mutual trustedBotIds
  2. In a shared channel, send: @超渡法師 you can see @普渡法師 right?
  3. 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:

  1. Removes only the bot's own trigger mention (<@BOT_ID>)
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingneeds-triagep1High — address this sprint

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions