Skip to content

fix(slack): detect implicit thread mention when bot participated but did not start thread [claude, human developer oversight]#30255

Closed
zeroaltitude wants to merge 7 commits intoopenclaw:mainfrom
zeroaltitude:fix/slack-implicit-thread-mention
Closed

fix(slack): detect implicit thread mention when bot participated but did not start thread [claude, human developer oversight]#30255
zeroaltitude wants to merge 7 commits intoopenclaw:mainfrom
zeroaltitude:fix/slack-implicit-thread-mention

Conversation

@zeroaltitude
Copy link
Copy Markdown
Contributor

@zeroaltitude zeroaltitude commented Mar 1, 2026

fix(slack): detect implicit thread mention when bot participated but did not 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.

Summary

  • Problem: In channels with 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.
  • Why it matters: Users expect the bot to stay engaged in a thread once it has participated. Requiring a fresh @mention on every reply is unnatural and confusing.
  • What changed: Thread replies now check for prior bot participation via (1) existing thread session (cheap file read), then (2) Slack conversations.replies API (lightweight fallback, capped at 50 messages). Both checks are gated behind wouldRequireMention to avoid unnecessary work in channels that do not require mentions.
  • What did NOT change: Direct messages, parent-author detection, and non-threaded messages are unaffected. The conversations.replies API call only fires when there is no thread session and mention would otherwise be required.

Change Type

  • Bug fix

Scope

  • Integrations

User-visible / Behavior Changes

In Slack channels with requireMention: true (or defaultRequireMention: 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

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? Yes — conversations.replies API call added as fallback
  • Command/tool execution surface changed? No
  • Data access scope changed? No
  • Risk + mitigation: The new conversations.replies call reads thread messages to detect bot participation. It only fires when (a) in a room with requireMention, (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

  • 3 new test cases covering: session-based detection, API-based detection, and negative (no participation → skip)

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Failure Recovery

  • The conversations.replies fallback silently catches errors — if the API call fails, behavior reverts to the old require-explicit-mention path.
  • To fully disable: revert this commit.

Risks and Mitigations

  • Risk: Additional Slack API call per unrecognized thread reply in requireMention channels.
    • Mitigation: Only fires when no thread session exists (most threads will have one after the first exchange). Gated behind wouldRequireMention. Capped at 50 messages. Failure is silent.

AI-Assisted Development 🤖

This PR was developed with AI assistance (Claude/OpenClaw agent "Tank").

  • Degree of testing: Fully tested — 20 unit tests pass (including 4 new tests), pnpm check clean (tsgo + lint + format)
  • Human oversight: All changes reviewed and directed by Eddie Abrams (CIO, BigHat Biosciences). Code review comments from Codex were evaluated and applied where valid.
  • Understanding confirmed: The contributor understands the Slack message routing pipeline, requireMention gating, thread session mechanics, and the conversations.replies API fallback.

… 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.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 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,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@chatgpt-codex-connector Please review my fix.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 1, 2026

Greptile Summary

This PR fixes thread mention detection in Slack channels with requireMention enabled. Previously, follow-up thread replies were silently dropped unless the bot authored the parent message, even when the bot had already participated in the thread.

What Changed:

  • Thread replies now check for bot participation via (1) existing thread session (file read), then (2) Slack conversations.replies API (fallback)
  • Both checks are gated behind wouldRequireMention to avoid unnecessary work in channels without mention requirements
  • Added comprehensive test coverage: session-based detection, API-based detection, and negative case (no participation)

How It Works:
When a thread reply arrives in a requireMention channel where the bot didn't author the parent message, the code first checks if a thread session exists (indicating prior bot participation). If not found, it queries the Slack API to check if the bot has posted in the thread. This handles the gap where the bot's own outbound messages don't create inbound sessions.

Assessment:
The implementation is sound with proper error handling, performance gating, and comprehensive tests. The logic correctly handles the common case where users expect continued thread engagement after initially mentioning the bot.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • Well-structured bug fix with clear logic flow, appropriate performance mitigations (gated checks, 50-message limit), proper error handling (silent fallback), and comprehensive test coverage for all three scenarios (session-based, API-based, and negative case). No logical errors or security concerns identified.
  • No files require special attention

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.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 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".

Comment on lines +219 to +224
if (
!isDirectMessage &&
ctx.botUserId &&
isThreadReply &&
!botIsParentAuthor &&
wouldRequireMention
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@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.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 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".

Comment on lines +238 to +243
if (!botParticipatedInThread && threadTs && ctx.app?.client?.conversations?.replies) {
try {
const threadReplies = await ctx.app.client.conversations.replies({
channel: message.channel,
ts: threadTs,
limit: 50,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@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.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 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".

Comment on lines +257 to +258
if (!botParticipatedInThread) {
ctx.nonBotThreads.add(threadCacheKey);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@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.
@zeroaltitude zeroaltitude changed the title fix(slack): detect implicit thread mention when bot participated but did not start thread fix(slack): detect implicit thread mention when bot participated but did not start thread [claude, human developer oversight] Mar 1, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 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".

Comment on lines +243 to +244
!ctx.nonBotThreads.peek(threadCacheKey) &&
ctx.app?.client?.conversations?.replies
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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:

  1. readSessionUpdatedAt() checks if a thread session file exists (cheap file read)
  2. Only if no session exists does the code reach the nonBotThreads negative 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.

@zeroaltitude
Copy link
Copy Markdown
Contributor Author

Superceded!

@zeroaltitude zeroaltitude deleted the fix/slack-implicit-thread-mention branch March 2, 2026 21:46
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant