Skip to content

fix(slack): allow app_mention events to bypass dedup cache#20152

Closed
nova-openclaw-cgk wants to merge 2 commits intoopenclaw:mainfrom
nova-openclaw-cgk:fix/app-mention-dedup-race-condition
Closed

fix(slack): allow app_mention events to bypass dedup cache#20152
nova-openclaw-cgk wants to merge 2 commits intoopenclaw:mainfrom
nova-openclaw-cgk:fix/app-mention-dedup-race-condition

Conversation

@nova-openclaw-cgk
Copy link
Copy Markdown

@nova-openclaw-cgk nova-openclaw-cgk commented Feb 18, 2026

Summary

  • Exempts app_mention events from the message dedup cache in createSlackMessageHandler. Slack delivers both message and app_mention for the same @mention with no ordering guarantee. The shared channelId:ts dedup key causes the second event to be silently dropped ~50% of the time.
  • When message wins the race in a requireMention: true channel, it's dropped for lacking a mention but the dedup key is still set. The app_mention (with wasMentioned: true) is then deduped, causing missed mentions and lost thread context (invalid_thread_ts → fallback to main channel).
  • The fix is safe because the downstream createInboundDebouncer already handles dedup at the flush level via buildKey, and prepareSlackMessage correctly merges wasMentioned across debounced entries.

Test plan

  • Verify existing vitest tests pass (src/slack/monitor/message-handler/prepare.test.ts, src/slack/monitor/monitor.test.ts)
  • In a channel with requireMention: true, @mention the bot — confirm it responds every time (not ~50%)
  • @mention the bot in a thread reply — confirm response stays in the thread (no invalid_thread_ts fallback)
  • Send a message without @mention in a requireMention: true channel — confirm bot still ignores it
  • Confirm no double-processing: bot should respond once per @mention, not twice

Fixes #20151

🤖 Generated with Claude Code

Greptile Summary

This PR fixes a race condition where Slack's app_mention events were being silently dropped ~50% of the time due to a shared dedup cache key (channelId:ts) with message events. In requireMention: true channels, if the message event won the race it would set the dedup key (but be dropped for lacking a mention), causing the subsequent app_mention to be deduped — resulting in missed mentions and invalid_thread_ts errors.

The fix exempts app_mention events from the markMessageSeen dedup check entirely, relying on the downstream createInboundDebouncer to handle dedup at the flush level.

  • The fix correctly solves the core issue for requireMention: true channels
  • There is a potential double-processing concern: when debounceMs is 0 (the default), both message and app_mention events will independently reach dispatch in channels where requireMention is false, since the debouncer does not buffer at zero debounce
  • A safer approach would be to still call markMessageSeen for app_mention events (to poison the key for the competing message event) while skipping the early-return, reducing the double-processing window

Confidence Score: 3/5

  • The fix addresses the critical missed-mention bug but may introduce double-processing in non-requireMention channels with default (zero) debounce.
  • The change correctly fixes the described race condition for requireMention channels, which is the primary use case. However, the complete bypass of markMessageSeen for app_mention events (neither checking nor setting the key) means both message and app_mention can independently reach dispatch when debounceMs is 0 (the default). This could cause duplicate bot responses in channels where requireMention is false.
  • src/slack/monitor/message-handler.ts — the dedup bypass logic at line 119 needs attention for the double-processing edge case.

Last reviewed commit: 78d7065

(2/5) Greptile learns from your feedback when you react with thumbs up/down!

Slack delivers both `message` and `app_mention` events for the same
@mention with no guaranteed delivery order. The existing dedup cache
uses the same `channelId:ts` key for both event types, so whichever
arrives first marks the message as seen and the second is silently
dropped.

When `message` arrives first in a channel with `requireMention: true`,
it is dropped for lacking a mention — but the dedup key is still set.
The subsequent `app_mention` (which carries `wasMentioned: true`) is
then deduped, causing:

1. @mentions silently ignored (~50% of the time)
2. Thread context never established (threadTsResolver.resolve never runs)
3. `invalid_thread_ts` errors on reply → fallback to main channel

The fix exempts `app_mention` events from the dedup check. This is safe
because the downstream debouncer already merges duplicate entries via
`buildKey`, and `prepareSlackMessage` correctly combines `wasMentioned`
across debounced entries.

Fixes openclaw#20151
@openclaw-barnacle openclaw-barnacle bot added channel: slack Channel integration: slack size: XS labels Feb 18, 2026
Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +119 to 121
if (opts.source !== "app_mention" && ctx.markMessageSeen(message.channel, message.ts)) {
return;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double-processing risk when debounce is 0

When debounceMs is 0 (the default per resolveInboundDebounceMs), the debouncer flushes each event immediately without buffering. Since app_mention now bypasses both the check and the set of markMessageSeen, both message and app_mention events will independently reach prepareSlackMessage and dispatchPreparedSlackMessage.

In requireMention: true channels the message event is dropped by the mention gate, so there's no issue. But in channels where requireMention is false (or DMs), both events pass through, potentially causing the bot to respond twice to the same @mention.

Consider still calling markMessageSeen for app_mention (to set the key for the subsequent message event) while skipping the early-return when the key was already set:

Suggested change
if (opts.source !== "app_mention" && ctx.markMessageSeen(message.channel, message.ts)) {
return;
}
// Allow app_mention events to bypass dedup. Slack sends both `message` and
// `app_mention` for the same @mention with no delivery-order guarantee. If
// the `message` event arrives first it is marked seen; without this guard the
// subsequent `app_mention` (which carries the authoritative `wasMentioned`
// flag) would be silently dropped, causing missed mentions and lost thread
// context. See: https://github.com/openclaw/openclaw/issues/20151
const alreadySeen = ctx.markMessageSeen(message.channel, message.ts);
if (alreadySeen && opts.source !== "app_mention") {
return;
}

This way, whichever event arrives first marks the key as seen, and the second is deduped — unless it's app_mention, which always proceeds. If app_mention arrives first it marks the key, so the subsequent message event is deduped. If message arrives first it marks the key and proceeds, but app_mention also proceeds — however in non-requireMention channels the message event would have already handled the message, and the debouncer would merge them if debounce > 0. For debounce=0 with message winning the race, both still process, but at least the app_mention-first ordering is safe.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/slack/monitor/message-handler.ts
Line: 119-121

Comment:
**Double-processing risk when debounce is 0**

When `debounceMs` is 0 (the default per `resolveInboundDebounceMs`), the debouncer flushes each event immediately without buffering. Since `app_mention` now bypasses **both** the check and the set of `markMessageSeen`, both `message` and `app_mention` events will independently reach `prepareSlackMessage` and `dispatchPreparedSlackMessage`.

In `requireMention: true` channels the `message` event is dropped by the mention gate, so there's no issue. But in channels where `requireMention` is `false` (or DMs), both events pass through, potentially causing the bot to respond twice to the same @mention.

Consider still calling `markMessageSeen` for `app_mention` (to set the key for the subsequent `message` event) while skipping the early-return when the key was already set:

```suggestion
    // Allow app_mention events to bypass dedup. Slack sends both `message` and
    // `app_mention` for the same @mention with no delivery-order guarantee. If
    // the `message` event arrives first it is marked seen; without this guard the
    // subsequent `app_mention` (which carries the authoritative `wasMentioned`
    // flag) would be silently dropped, causing missed mentions and lost thread
    // context.  See: https://github.com/openclaw/openclaw/issues/20151
    const alreadySeen = ctx.markMessageSeen(message.channel, message.ts);
    if (alreadySeen && opts.source !== "app_mention") {
      return;
    }
```

This way, whichever event arrives first marks the key as seen, and the second is deduped — unless it's `app_mention`, which always proceeds. If `app_mention` arrives first it marks the key, so the subsequent `message` event is deduped. If `message` arrives first it marks the key and proceeds, but `app_mention` also proceeds — however in non-`requireMention` channels the `message` event would have already handled the message, and the debouncer would merge them if debounce > 0. For debounce=0 with `message` winning the race, both still process, but at least the `app_mention`-first ordering is safe.

How can I resolve this? If you propose a fix, please make it concise.

@unboxed-ai
Copy link
Copy Markdown

Hey 👋 — we're running into this exact issue in production (multi-agent Slack setup where agents @mention each other). The race between message and app_mention events causes ~50% of inter-agent mentions to be silently dropped.

We've cherry-picked this fix into our fork and can confirm it resolves the issue cleanly. The downstream debouncer handles any duplicate concern.

Is there anything blocking this from being merged? Happy to address review feedback if needed.

@openclaw-barnacle
Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle bot added the stale Marked as stale due to inactivity label Mar 18, 2026
@openclaw-barnacle
Copy link
Copy Markdown

Closing due to inactivity.
If you believe this PR should be revived, post in #pr-thunderdome-dangerzone on Discord to talk to a maintainer.
That channel is the escape hatch for high-quality PRs that get auto-closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: slack Channel integration: slack size: XS stale Marked as stale due to inactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants