fix(slack): detect implicit thread mention when bot participated but did not start thread [claude, human developer oversight]#30255
Conversation
… start thread When requireMention is enabled, thread replies were only routed to the bot if it authored the parent message. This missed the common case where a user @mentions the bot in a channel, the bot replies in-thread, and subsequent thread replies should route without repeated @mentions. Now checks for bot participation via: 1. Existing thread session (cheap file read) 2. Slack conversations.replies API (lightweight fallback) Both checks are gated behind wouldRequireMention to avoid unnecessary API calls in channels that don't require mentions. Includes 3 test cases covering session-based, API-based, and negative (no participation) scenarios. Co-authored-by: Eddie Abrams <eddie@bighatbio.com>
The mock appClient objects only implement conversations.replies, so TypeScript rejects a direct cast to App["client"]. Cast through unknown first to satisfy the type checker.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 13032d1b9c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| limit: 50, | ||
| }); | ||
| botParticipatedInThread = (threadReplies.messages ?? []).some( | ||
| (m: { user?: string }) => m.user === ctx.botUserId, |
There was a problem hiding this comment.
Match bot thread replies by bot identity, not only user
The new conversations.replies fallback only treats a message as bot participation when m.user === ctx.botUserId, but Slack thread replies can represent bot posts as bot messages without a user field (with bot_id instead), which this codebase already accounts for in thread-history parsing. In that common payload shape, this predicate never matches, so follow-up replies without a fresh mention are still skipped even after the bot has replied in-thread; please include bot-message identity in this participation check.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
@chatgpt-codex-connector Please review my fix.
Greptile SummaryThis PR fixes thread mention detection in Slack channels with What Changed:
How It Works: Assessment: Confidence Score: 5/5
Last reviewed commit: bad5596 |
Bot messages in Slack threads can appear with only bot_id (no user field). Thread the bot's bot_id from auth.test through the monitor context so the conversations.replies participation check matches both m.user === botUserId and m.bot_id === botId.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 315e40ac51
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if ( | ||
| !isDirectMessage && | ||
| ctx.botUserId && | ||
| isThreadReply && | ||
| !botIsParentAuthor && | ||
| wouldRequireMention |
There was a problem hiding this comment.
Skip participation lookup for already-mentioned thread replies
When requireMention is enabled, this condition triggers the thread participation path even if wasMentioned is already true (for example, app_mention events or explicit <@bot> replies). In those cases the message would pass mention-gating regardless, so the extra conversations.replies call is unnecessary and adds avoidable network latency/rate-limit pressure to normal thread traffic. Gate this block with !wasMentioned so the fallback lookup only runs when it can change the routing decision.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
@chatgpt-codex-connector Please review my fix.
If the message already has an explicit @mention, the participation check is unnecessary — it would pass mention-gating regardless. Gate with !wasMentioned to avoid the extra session read and conversations.replies API call.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0db6371186
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (!botParticipatedInThread && threadTs && ctx.app?.client?.conversations?.replies) { | ||
| try { | ||
| const threadReplies = await ctx.app.client.conversations.replies({ | ||
| channel: message.channel, | ||
| ts: threadTs, | ||
| limit: 50, |
There was a problem hiding this comment.
Avoid probing Slack replies for every unmentioned thread post
This fallback currently calls conversations.replies on each unmentioned thread reply in rooms with requireMention when no thread session exists, but threads that never involved the bot never create a session, so unrelated thread traffic repeatedly triggers API reads before the message is later skipped by mention-gating. In busy Slack channels, this can create sustained extra API load and increase rate-limit pressure for real bot operations; add a negative cache/guard so non-bot threads are not re-fetched on every message.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
@chatgpt-codex-connector Please review my fix.
In busy channels with requireMention, every unmentioned thread reply in a non-bot thread was calling conversations.replies repeatedly just to get the same negative result. Add an in-memory Set on the monitor context that caches 'no bot participation' results so the API is only called once per thread. Includes test verifying the cache prevents repeated API calls.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ee09462bce
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (!botParticipatedInThread) { | ||
| ctx.nonBotThreads.add(threadCacheKey); |
There was a problem hiding this comment.
Expire negative thread-participation cache entries
ctx.nonBotThreads only grows via add and has no eviction/invalidation path, so long-lived Slack monitors will accumulate one key per missed thread forever, increasing memory/GC pressure over time. Because the miss state is permanent, the cache can also become stale: once a thread is marked non-bot, later unmentioned replies will keep skipping conversations.replies, so the thread can stay incorrectly gated if bot participation appears later without a persisted inbound thread session. Use a bounded TTL/LRU cache or clear entries when bot activity is observed.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
@chatgpt-codex-connector Please review my fix.
Replace unbounded Set<string> with createDedupeCache (10 min TTL, 1000 max entries) so long-lived Slack monitors don't accumulate thread keys indefinitely. Also handles staleness — entries expire, allowing re-checks if bot participation appears later. Addresses Codex review feedback on openclaw#30255.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a2dbfd58d9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| !ctx.nonBotThreads.peek(threadCacheKey) && | ||
| ctx.app?.client?.conversations?.replies |
There was a problem hiding this comment.
Recheck cached miss after thread gains bot response
The negative cache check can permanently suppress implicit-mention detection for active threads: once a thread is marked non-bot, later replies skip conversations.replies entirely, so a thread where the bot joins after that first miss will keep being dropped until TTL expiry. This is reproducible when an unmentioned thread reply arrives before the bot’s first threaded response (cache miss recorded), then the bot replies (self-messages are filtered before session recording), and subsequent unmentioned replies are still treated as non-bot because this guard prevents revalidation.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Thanks for the analysis, but I believe the existing code flow already prevents this scenario.
The negative cache (line 243) is only consulted after the thread session check (line 231). Here's the sequence:
readSessionUpdatedAt()checks if a thread session file exists (cheap file read)- Only if no session exists does the code reach the
nonBotThreadsnegative cache
When the bot replies to a thread, the inbound user message that triggered the reply creates a thread session during routing. So by the time the next unmentioned reply arrives, readSessionUpdatedAt() returns a timestamp → botParticipatedInThread = true, and we never reach the negative cache check at all.
The only edge case would be if the bot joins a thread via an external mechanism that doesn't create a session (e.g., a webhook posting as the bot). That's extremely narrow, and the 10-minute TTL on the DedupeCache handles it regardless.
So the cache staleness concern doesn't apply in practice — the session check gates it.
…ted our fix via sent-thread-cache)
|
Superceded! |
fix(slack): detect implicit thread mention when bot participated but did not start thread
When
requireMentionis enabled, thread replies were only routed to the bot if it authored the parent message. This missed the common case where a user @mentions the bot in a channel, the bot replies in-thread, and subsequent thread replies should route without repeated @mentions.Summary
requireMention: true, follow-up thread replies were silently dropped unless the bot authored the parent message — even if the bot had already replied in the thread.conversations.repliesAPI (lightweight fallback, capped at 50 messages). Both checks are gated behindwouldRequireMentionto avoid unnecessary work in channels that do not require mentions.conversations.repliesAPI call only fires when there is no thread session and mention would otherwise be required.Change Type
Scope
User-visible / Behavior Changes
In Slack channels with
requireMention: true(ordefaultRequireMention: true), thread replies now route to the bot when the bot previously participated in the thread, even if the bot did not author the parent message. Previously these replies were silently dropped.Security Impact
conversations.repliesAPI call added as fallbackconversations.repliescall reads thread messages to detect bot participation. It only fires when (a) in a room withrequireMention, (b) bot is not the parent author, (c) no thread session exists. Capped at 50 messages. Failure is caught silently (falls through to require explicit mention).Evidence
Compatibility / Migration
Failure Recovery
conversations.repliesfallback silently catches errors — if the API call fails, behavior reverts to the old require-explicit-mention path.Risks and Mitigations
requireMentionchannels.wouldRequireMention. Capped at 50 messages. Failure is silent.AI-Assisted Development 🤖
This PR was developed with AI assistance (Claude/OpenClaw agent "Tank").
pnpm checkclean (tsgo + lint + format)requireMentiongating, thread session mechanics, and theconversations.repliesAPI fallback.