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_mention never reaches threadTsResolver.resolve(), so thread sessions are never created
invalid_thread_ts errors — 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:
message event arrives first → dropped by requireMention check (in prepareSlackMessage) → but still marked as seen in dedup cache
app_mention arrives with wasMentioned: 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_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
Summary
When a user @mentions the bot in a Slack channel (especially in thread replies), Slack delivers two events for the same message:
messageandapp_mention. OpenClaw's dedup cache (markMessageSeen) uses the samechannelId:tskey for both event types. If themessageevent arrives first and is processed (or dropped forrequireMention), theapp_mentionevent is silently deduped — even though it carries the criticalwasMentioned: trueflag.This causes:
app_mentionnever reachesthreadTsResolver.resolve(), so thread sessions are never createdinvalid_thread_tserrors — when the agent eventually tries to reply, the thread_ts is missing, causing fallback to the main channelReproduction
requireMention: true(or use the default)Root Cause
In
src/slack/monitor/message-handler.ts, thecreateSlackMessageHandlerreturn function:Slack provides no ordering guarantees between
messageandapp_mentionevents (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 droppedinvalid_thread_ts→ fallback to main channelProposed Fix
app_mentionevents should bypass the dedup cache since they carry authoritative mention information:This is safe because:
app_mentionarrives first: it processes normally, thenmessageis deduped (correct)messagearrives first: it processes (may be dropped byrequireMention), thenapp_mentionstill processes withwasMentioned: true(correct)buildKey, andprepareSlackMessagemergeswasMentionedacross debounced entriesEvidence from Logs
Session key had no
:thread:suffix, confirming thread context was never resolved.Environment
requireMention: true(default), channel-specificrequireMention: trueRelated Issues