-
-
Notifications
You must be signed in to change notification settings - Fork 69.2k
[Bug]: app_mention events silently dropped by dedup cache when message event arrives first #20151
Description
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:
- @mentions silently ignored — the bot never responds to direct mentions
- Thread context lost —
app_mentionnever reachesthreadTsResolver.resolve(), so thread sessions are never created invalid_thread_tserrors — when the agent eventually tries to reply, the thread_ts is missing, causing fallback to the main channel
Reproduction
- Configure a channel with
requireMention: true(or use the default) - Send a message in that channel @mentioning the bot
- Observe that ~50% of the time, the bot doesn't respond at all
- 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:
messageevent arrives first → dropped byrequireMentioncheck (inprepareSlackMessage) → but still marked as seen in dedup cacheapp_mentionarrives withwasMentioned: true→ deduped and silently dropped- Thread session never created (thread context resolution never runs)
- 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_mentionarrives first: it processes normally, thenmessageis deduped (correct) - If
messagearrives first: it processes (may be dropped byrequireMention), thenapp_mentionstill processes withwasMentioned: true(correct) - Double-processing risk is minimal: the debouncer already handles dedup at the flush level via
buildKey, andprepareSlackMessagemergeswasMentionedacross 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-specificrequireMention: true
Related Issues
- [Bug]: Slack thread context silently lost when thread_ts resolution fails (~10% of threads) #12389 — Thread context silently lost (~10% of threads) — likely same root cause
- Slack thread replies leak to main channel - deliveryContext missing thread_ts #10837 — Slack thread replies leak to main channel
- [Bug]: Slack thread_ts lost during collect queue drain — typeof === "number" check drops string thread IDs #4380 — thread_ts lost during collect queue drain