Skip to content

[Bug]: app_mention events silently dropped by dedup cache when message event arrives first #20151

@nova-openclaw-cgk

Description

@nova-openclaw-cgk

Summary

When a user @mentions the bot in a Slack channel (especially in thread replies), Slack delivers two events for the same message: message and app_mention. OpenClaw's dedup cache (markMessageSeen) uses the same channelId:ts key for both event types. If the message event arrives first and is processed (or dropped for requireMention), the app_mention event is silently deduped — even though it carries the critical wasMentioned: true flag.

This causes:

  1. @mentions silently ignored — the bot never responds to direct mentions
  2. Thread context lostapp_mention never reaches threadTsResolver.resolve(), so thread sessions are never created
  3. invalid_thread_ts errors — when the agent eventually tries to reply, the thread_ts is missing, causing fallback to the main channel

Reproduction

  1. Configure a channel with requireMention: true (or use the default)
  2. Send a message in that channel @mentioning the bot
  3. Observe that ~50% of the time, the bot doesn't respond at all
  4. In thread replies with @mention, the response often leaks to the main channel instead of the thread

Root Cause

In src/slack/monitor/message-handler.ts, the createSlackMessageHandler return function:

return async (message, opts) => {
    if (opts.source === "message" && message.type !== "message") {
      return;
    }
    if (
      opts.source === "message" &&
      message.subtype &&
      message.subtype !== "file_share" &&
      message.subtype !== "bot_message"
    ) {
      return;
    }
    // BUG: This dedup check uses channelId:ts for BOTH event types.
    // If `message` event arrives first, `app_mention` is silently dropped.
    if (ctx.markMessageSeen(message.channel, message.ts)) {
      return;
    }
    const resolvedMessage = await threadTsResolver.resolve({ message, source: opts.source });
    await debouncer.enqueue({ message: resolvedMessage, opts });
};

Slack provides no ordering guarantees between message and app_mention events (confirmed by Slack team). The dedup cache has a 60-second TTL with 500 entries, so whichever event arrives first "wins" and the second is always dropped.

The cascade failure for thread replies:

  1. message event arrives first → dropped by requireMention check (in prepareSlackMessage) → but still marked as seen in dedup cache
  2. app_mention arrives with wasMentioned: truededuped and silently dropped
  3. Thread session never created (thread context resolution never runs)
  4. Agent responds without thread context → Slack API returns invalid_thread_ts → fallback to main channel

Proposed Fix

app_mention events should bypass the dedup cache since they carry authoritative mention information:

if (opts.source !== "app_mention" && ctx.markMessageSeen(message.channel, message.ts)) {
    return;
}

This is safe because:

  • If app_mention arrives first: it processes normally, then message is deduped (correct)
  • If message arrives first: it processes (may be dropped by requireMention), then app_mention still processes with wasMentioned: true (correct)
  • Double-processing risk is minimal: the debouncer already handles dedup at the flush level via buildKey, and prepareSlackMessage merges wasMentioned across debounced entries

Evidence from Logs

15:44:09 UTC  {"channel":"C0ADZGUJS4A","reason":"no-mention"} "skipping channel message"
              ^ message event dropped for no-mention (but already marked seen)

15:45:03 UTC  "slack-stream: streaming API call failed: Error: An API error occurred: invalid_thread_ts"
              ^ agent tried to reply but thread context was never established

15:45:03 UTC  "delivered reply to channel:C0ADZGUJS4A"
              ^ fallback to main channel instead of thread

Session key had no :thread: suffix, confirming thread context was never resolved.

Environment

  • OpenClaw version: 2026.2.17
  • OS: macOS (Darwin 24.3.0)
  • Channel: Slack (Socket Mode)
  • Config: requireMention: true (default), channel-specific requireMention: true

Related Issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions