feat(slack): track thread participation for auto-reply without @mention#29165
Conversation
Greptile SummaryAdds in-memory cache to track Slack thread participation so the bot auto-responds in threads it has joined without requiring Key Changes:
Notes:
Confidence Score: 4/5
Last reviewed commit: 4acee55 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4acee557ef
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (anyReplyDelivered && statusThreadTs) { | ||
| recordSlackThreadParticipation(message.channel, statusThreadTs); |
There was a problem hiding this comment.
Record participation from actual reply thread in first mode
This only records participation when statusThreadTs is set, but for top-level messages in replyToMode: "first", resolveSlackThreadTargets leaves statusThreadTs undefined while the first reply is still sent into a thread via replyPlan.nextThreadTs() (see src/slack/threading.ts and src/slack/monitor/replies.ts). As a result, successful first threaded auto-replies are never cached, so later messages in that same thread still fail mention-gating unless users @mention the bot, which breaks the feature for first-mode configurations.
Useful? React with 👍 / 👎.
src/slack/sent-thread-cache.ts
Outdated
| function makeKey(channelId: string, threadTs: string): string { | ||
| return `${channelId}:${threadTs}`; |
There was a problem hiding this comment.
Scope thread participation cache by bot/account identity
The cache key is only channelId:threadTs, so participation state is shared across all configured Slack bots/accounts in the same process. In multi-bot setups where two Slack accounts monitor the same workspace/channel, one bot replying in a thread will make hasSlackThreadParticipation(...) true for the other bot as well, causing unsolicited implicit-mention replies from bots that never participated in that thread.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0e9c47a2ca
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const replyThreadTs = forcedThreadTs ?? replyPlan.nextThreadTs(); | ||
| if (replyThreadTs) { | ||
| usedReplyThreadTs ??= replyThreadTs; | ||
| } |
There was a problem hiding this comment.
Capture thread ID for streamed/thread-edited first replies
In replyToMode: "first", thread participation is only latched when usedReplyThreadTs is set here, but successful native-streaming starts (startSlackStream) and preview-final-edit paths can send the first threaded reply without calling deliverNormally. For top-level messages those paths also have no statusThreadTs, so the participation cache never records the thread and subsequent user replies in that thread still fail implicit-mention gating unless they @mention the bot. Fresh evidence: this commit’s new usedReplyThreadTs assignment exists only inside deliverNormally, while streaming-start and preview-edit paths bypass it.
Useful? React with 👍 / 👎.
| const participationThreadTs = usedReplyThreadTs ?? statusThreadTs; | ||
| if (anyReplyDelivered && participationThreadTs) { | ||
| recordSlackThreadParticipation(account.accountId, message.channel, participationThreadTs); |
There was a problem hiding this comment.
Update participation cache only after confirmed send success
This marks a thread as participated based on anyReplyDelivered, but that signal is derived from queued reply counts rather than successful Slack deliveries, so transient chat.postMessage/stream API failures can still populate the participation cache. In that failure mode, later messages in the thread may bypass mention-gating even though the bot never actually posted in the thread.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6d811d5712
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| @@ -208,7 +212,8 @@ export async function prepareSlackMessage(params: { | |||
| !isDirectMessage && | |||
| ctx.botUserId && | |||
There was a problem hiding this comment.
Allow participation cache when bot user ID is unavailable
The new implicit-mention logic still short-circuits on ctx.botUserId, so the participation cache is never consulted when Slack auth.test fails at startup (which src/slack/monitor/provider.ts treats as non-fatal by leaving botUserId empty). In that degraded-auth scenario, threads where the bot already replied (and were recorded via recordSlackThreadParticipation) still require explicit @mentions, so this feature silently stops working precisely when mention detection is already in fallback mode.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
The ctx.botUserId && guard is pre-existing — it was there before this PR for the parent_user_id === ctx.botUserId check. Removing it to enable the participation cache in degraded-auth mode would change unrelated behavior and is out of scope for this change. If degraded-auth implicit mentions need fixing, that should be a separate PR.
|
Noting that all Codex-identified issues tied to his PR have been resolved. I did run CodeScene on the files I updated with this update, and found that there's high cyclomatic complexity in the |
… actual reply thread ts
6d811d5 to
5079d0c
Compare
|
Merged via salvage path. Merge commit: bd78a74 What changed before merge:
Verification run on the rebased branch:
All checks passed. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5079d0c4c3
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // Record the thread ts only after confirmed delivery success. | ||
| if (replyThreadTs) { | ||
| usedReplyThreadTs ??= replyThreadTs; |
There was a problem hiding this comment.
Capture thread participation when chunked send partially succeeds
usedReplyThreadTs is only assigned after deliverReplies(...) resolves, but deliverReplies can send multiple Slack messages for one payload (media fan-out and chunked text) and will throw if a later API call fails after earlier messages were already posted. In that partial-success path the bot has participated in the thread, yet the cache is never updated, so subsequent thread messages can still be mention-gated unexpectedly.
Useful? React with 👍 / 👎.
| blocks, | ||
| }); | ||
|
|
||
| if (threadTs && result.channelId && account.accountId) { | ||
| recordSlackThreadParticipation(account.accountId, result.channelId, threadTs); |
There was a problem hiding this comment.
Record participation even when tool send fails after first chunk
This records participation only after sendSlackMessage(...) returns, but that helper sends chunked/media replies as multiple Slack API calls and may throw on a later call after at least one message was already posted. When that happens, the tool action fails while the bot has still replied in-thread, and the participation cache remains empty, so follow-up thread messages keep requiring explicit @mentions.
Useful? React with 👍 / 👎.
…on (openclaw#29165) Cherry-pick of upstream bd78a74.
…on (openclaw#29165) * feat(slack): track thread participation for auto-reply without @mention * fix(slack): scope thread participation cache by accountId and capture actual reply thread ts * fix(slack): capture reply thread ts from all delivery paths and only after success * Slack: add changelog for thread participation cache behavior --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
…on (openclaw#29165) * feat(slack): track thread participation for auto-reply without @mention * fix(slack): scope thread participation cache by accountId and capture actual reply thread ts * fix(slack): capture reply thread ts from all delivery paths and only after success * Slack: add changelog for thread participation cache behavior --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
…on (openclaw#29165) * feat(slack): track thread participation for auto-reply without @mention * fix(slack): scope thread participation cache by accountId and capture actual reply thread ts * fix(slack): capture reply thread ts from all delivery paths and only after success * Slack: add changelog for thread participation cache behavior --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
…on (openclaw#29165) * feat(slack): track thread participation for auto-reply without @mention * fix(slack): scope thread participation cache by accountId and capture actual reply thread ts * fix(slack): capture reply thread ts from all delivery paths and only after success * Slack: add changelog for thread participation cache behavior --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
…on (openclaw#29165) * feat(slack): track thread participation for auto-reply without @mention * fix(slack): scope thread participation cache by accountId and capture actual reply thread ts * fix(slack): capture reply thread ts from all delivery paths and only after success * Slack: add changelog for thread participation cache behavior --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
PR openclaw#29165 added always-on thread participation auto-reply that bypasses requireMention gating with no opt-out. Add a config option under channels.slack.thread to disable this behavior: channels.slack.thread.autoReplyOnParticipation: false When false, hasSlackThreadParticipation() is skipped and implicitMention only fires for parent_user_id === botUserId (the pre-openclaw#29165 behavior). Default: true (preserves current behavior). Closes openclaw#31728 ✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)
PR openclaw#29165 added always-on thread participation auto-reply that bypasses requireMention gating with no opt-out. Add a config option under channels.slack.thread to disable this behavior: channels.slack.thread.autoReplyOnParticipation: false When false, hasSlackThreadParticipation() is skipped and implicitMention only fires for parent_user_id === botUserId (the pre-openclaw#29165 behavior). Default: true (preserves current behavior). Closes openclaw#31728 ✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)
…on (openclaw#29165) * feat(slack): track thread participation for auto-reply without @mention * fix(slack): scope thread participation cache by accountId and capture actual reply thread ts * fix(slack): capture reply thread ts from all delivery paths and only after success * Slack: add changelog for thread participation cache behavior --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
PR openclaw#29165 added always-on thread participation auto-reply that bypasses requireMention gating with no opt-out. Add a config option under channels.slack.thread to disable this behavior: channels.slack.thread.autoReplyOnParticipation: false When false, hasSlackThreadParticipation() is skipped and implicitMention only fires for parent_user_id === botUserId (the pre-openclaw#29165 behavior). Default: true (preserves current behavior). Closes openclaw#31728 ✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)
…on (openclaw#29165) * feat(slack): track thread participation for auto-reply without @mention * fix(slack): scope thread participation cache by accountId and capture actual reply thread ts * fix(slack): capture reply thread ts from all delivery paths and only after success * Slack: add changelog for thread participation cache behavior --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
…on (openclaw#29165) * feat(slack): track thread participation for auto-reply without @mention * fix(slack): scope thread participation cache by accountId and capture actual reply thread ts * fix(slack): capture reply thread ts from all delivery paths and only after success * Slack: add changelog for thread participation cache behavior --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
…on (openclaw#29165) * feat(slack): track thread participation for auto-reply without @mention * fix(slack): scope thread participation cache by accountId and capture actual reply thread ts * fix(slack): capture reply thread ts from all delivery paths and only after success * Slack: add changelog for thread participation cache behavior --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
…on (openclaw#29165) * feat(slack): track thread participation for auto-reply without @mention * fix(slack): scope thread participation cache by accountId and capture actual reply thread ts * fix(slack): capture reply thread ts from all delivery paths and only after success * Slack: add changelog for thread participation cache behavior --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
PR openclaw#29165 added always-on thread participation auto-reply that bypasses requireMention gating with no opt-out. Add a config option under channels.slack.thread to disable this behavior: channels.slack.thread.autoReplyOnParticipation: false When false, hasSlackThreadParticipation() is skipped and implicitMention only fires for parent_user_id === botUserId (the pre-openclaw#29165 behavior). Default: true (preserves current behavior). Closes openclaw#31728 ✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)
PR openclaw#29165 added always-on thread participation auto-reply that bypasses requireMention gating with no opt-out. Add a config option under channels.slack.thread to disable this behavior: channels.slack.thread.autoReplyOnParticipation: false When false, hasSlackThreadParticipation() is skipped and implicitMention only fires for parent_user_id === botUserId (the pre-openclaw#29165 behavior). Default: true (preserves current behavior). Closes openclaw#31728 ✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)
PR openclaw#29165 added always-on thread participation auto-reply that bypasses requireMention gating with no opt-out. Add a config option under channels.slack.thread to disable this behavior: channels.slack.thread.autoReplyOnParticipation: false When false, hasSlackThreadParticipation() is skipped and implicitMention only fires for parent_user_id === botUserId (the pre-openclaw#29165 behavior). Default: true (preserves current behavior). Closes openclaw#31728 ✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)
PR openclaw#29165 added always-on thread participation auto-reply that bypasses requireMention gating with no opt-out. Add a config option under channels.slack.thread to disable this behavior: channels.slack.thread.autoReplyOnParticipation: false When false, hasSlackThreadParticipation() is skipped and implicitMention only fires for parent_user_id === botUserId (the pre-openclaw#29165 behavior). Default: true (preserves current behavior). Closes openclaw#31728 ✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)
PR openclaw#29165 added always-on thread participation auto-reply that bypasses requireMention gating with no opt-out. Add a config option under channels.slack.thread to disable this behavior: channels.slack.thread.autoReplyOnParticipation: false When false, hasSlackThreadParticipation() is skipped and implicitMention only fires for parent_user_id === botUserId (the pre-openclaw#29165 behavior). Default: true (preserves current behavior). Closes openclaw#31728 ✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)
PR openclaw#29165 added always-on thread participation auto-reply that bypasses requireMention gating with no opt-out. Add a config option under channels.slack.thread to disable this behavior: channels.slack.thread.autoReplyOnParticipation: false When false, hasSlackThreadParticipation() is skipped and implicitMention only fires for parent_user_id === botUserId (the pre-openclaw#29165 behavior). Default: true (preserves current behavior). Closes openclaw#31728 ✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)
PR openclaw#29165 added always-on thread participation auto-reply that bypasses requireMention gating with no opt-out. Add a config option under channels.slack.thread to disable this behavior: channels.slack.thread.autoReplyOnParticipation: false When false, hasSlackThreadParticipation() is skipped and implicitMention only fires for parent_user_id === botUserId (the pre-openclaw#29165 behavior). Default: true (preserves current behavior). Closes openclaw#31728 ✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)
…on (openclaw#29165) * feat(slack): track thread participation for auto-reply without @mention * fix(slack): scope thread participation cache by accountId and capture actual reply thread ts * fix(slack): capture reply thread ts from all delivery paths and only after success * Slack: add changelog for thread participation cache behavior --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
…on (openclaw#29165) * feat(slack): track thread participation for auto-reply without @mention * fix(slack): scope thread participation cache by accountId and capture actual reply thread ts * fix(slack): capture reply thread ts from all delivery paths and only after success * Slack: add changelog for thread participation cache behavior --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
…on (openclaw#29165) Cherry-pick of upstream bd78a74.
Summary
sendMessage) and monitor reply dispatchimplicitMentiondetectionChanges
src/slack/sent-thread-cache.ts— TTL-based (24h) in-memory cache with 5000 soft-cap, following the pattern from MS Teams and Telegram cachessrc/slack/sent-thread-cache.test.ts— 6 tests covering record/check, isolation, empty inputs, clearing, and TTL expirysrc/agents/tools/slack-actions.ts— records participation aftersendMessagewhenthreadTsandchannelIdare presentsrc/slack/monitor/message-handler/dispatch.ts— records participation after reply deliverysrc/slack/monitor/message-handler/prepare.ts— extendsimplicitMentionto include threads where the bot has participated (not just where it's the parent)Review
Verdict: APPROVE WITH SUGGESTIONS | Risk: 18/100
Review details
recordSlackThreadParticipationcall lacks dedicated unit testTesting
sent-thread-cache.test.ts)Breaking Changes
None
🤖 Generated with Claude Code