Skip to content

fix(slack): honour replyToMode off for typing indicator#18036

Closed
theoseo wants to merge 3 commits intoopenclaw:mainfrom
theoseo:fix/slack-reply-to-mode-status-thread
Closed

fix(slack): honour replyToMode off for typing indicator#18036
theoseo wants to merge 3 commits intoopenclaw:mainfrom
theoseo:fix/slack-reply-to-mode-status-thread

Conversation

@theoseo
Copy link
Contributor

@theoseo theoseo commented Feb 16, 2026

Summary

When replyToMode is "off" in DMs, replies should stay in the main conversation even when the typing indicator creates a thread context.

Problem

Previously, when incomingThreadTs was set (from the typing indicator's thread), replyToMode was forced to "all", causing all replies to go into the thread. This created an unexpected UX where:

  1. Typing indicator shows in a thread (expected Slack behavior)
  2. First reply goes to main channel (respects replyToMode: off)
  3. User replies → now has incomingThreadTs set
  4. Subsequent bot replies forced into thread (ignores replyToMode: off)

Solution

For direct messages, always respect the user's configured replyToMode. For channels/groups, preserve existing behavior (stay in thread if already in one).

// Before
const effectiveMode = params.incomingThreadTs ? "all" : params.replyToMode;

// After
const effectiveMode =
  params.chatType === "direct"
    ? params.replyToMode  // DMs: always respect user config
    : params.incomingThreadTs
      ? "all"           // Channels: stay in thread
      : params.replyToMode;

Changes

  • src/slack/monitor/replies.ts: Add chatType param, respect replyToMode in DMs
  • src/slack/monitor/message-handler/dispatch.ts: Pass chatType to reply planner

Testing

  • Unit tests pass
  • Manually verified in Slack DM:
    • ✅ Typing indicator shows
    • ✅ Replies stay in main conversation (no thread)

Fixes #16868

Greptile Summary

This PR fixes a bug where Slack DM replies were incorrectly forced into threads when replyToMode was set to "off". The typing indicator's status message creates a thread context (thread_ts), and previously this caused all subsequent replies to be threaded regardless of user configuration. The fix adds chatType and isThreadReply parameters to the reply planning logic, so that in DMs where the thread context comes from the typing indicator (not a real user thread reply), the configured replyToMode is respected.

  • src/slack/monitor/replies.ts: Extended createSlackReplyReferencePlanner and createSlackReplyDeliveryPlan with optional chatType and isThreadReply params. The effectiveMode logic now bypasses thread-sticking for DMs when isThreadReply is false.
  • src/slack/monitor/message-handler/dispatch.ts: Added a call to resolveSlackThreadContext to obtain isThreadReply, and passes both chatType and isThreadReply to the reply delivery plan.
  • No test changes were included in this PR. The new DM-specific behavior paths (chatType: "direct" with/without isThreadReply) are not covered by unit tests.

Confidence Score: 4/5

  • This PR is safe to merge — the logic change is narrowly scoped to DMs and the fallback preserves existing channel behavior.
  • The change is well-targeted: the new conditional only activates for DMs (chatType === "direct") with no real thread reply (!isThreadReply), and all other code paths remain identical. Default parameter values ensure backward compatibility for existing callers like resolveSlackThreadTs. The only concern is the absence of unit tests for the new DM-specific behavior paths, though the author reports manual verification.
  • No files require special attention — both files have small, well-scoped changes.

Last reviewed commit: 6d2a7d8

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

@openclaw-barnacle openclaw-barnacle bot added channel: slack Channel integration: slack size: XS labels Feb 16, 2026
@theoseo theoseo force-pushed the fix/slack-reply-to-mode-status-thread branch 2 times, most recently from 9e48d0b to 3411d7a Compare February 16, 2026 12:39
When replyToMode is 'off' in DMs, replies should stay in the main
conversation even when the typing indicator creates a thread context.

Previously, when incomingThreadTs was set (from the typing indicator's
thread), replyToMode was forced to 'all', causing all replies to go
into the thread.

Now, for direct messages, the user's configured replyToMode is always
respected. For channels/groups, the existing behavior is preserved
(stay in thread if already in one).

This fix:
- Keeps the typing indicator working (statusThreadTs fallback preserved)
- Prevents DM replies from being forced into threads
- Maintains channel thread continuity

Fixes openclaw#16868
@theoseo theoseo force-pushed the fix/slack-reply-to-mode-status-thread branch from 3411d7a to 6d2a7d8 Compare February 16, 2026 14:39
@steipete steipete closed this Feb 16, 2026
@steipete steipete reopened this Feb 17, 2026
Copy link
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.

2 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 17, 2026

Additional Comments (1)

src/slack/monitor/message-handler/dispatch.ts
Redundant resolveSlackThreadContext call

resolveSlackThreadTargets (line 38) already calls resolveSlackThreadContext internally and discards isThreadReply. You could avoid the duplicate computation by calling resolveSlackThreadContext once and deriving both statusThreadTs and isThreadReply from it:

  const threadCtx = resolveSlackThreadContext({
    message,
    replyToMode: ctx.replyToMode,
  });
  const { isThreadReply } = threadCtx;

Then replace the resolveSlackThreadTargets call above (lines 38-41) with inline computation:

const replyThreadTs = threadCtx.incomingThreadTs ?? (ctx.replyToMode === "all" ? threadCtx.messageTs : undefined);
const statusThreadTs = replyThreadTs ?? threadCtx.messageTs;

This eliminates the redundant context resolution.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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

Comment:
**Redundant `resolveSlackThreadContext` call**

`resolveSlackThreadTargets` (line 38) already calls `resolveSlackThreadContext` internally and discards `isThreadReply`. You could avoid the duplicate computation by calling `resolveSlackThreadContext` once and deriving both `statusThreadTs` and `isThreadReply` from it:

```suggestion
  const threadCtx = resolveSlackThreadContext({
    message,
    replyToMode: ctx.replyToMode,
  });
  const { isThreadReply } = threadCtx;
```

Then replace the `resolveSlackThreadTargets` call above (lines 38-41) with inline computation:

```ts
const replyThreadTs = threadCtx.incomingThreadTs ?? (ctx.replyToMode === "all" ? threadCtx.messageTs : undefined);
const statusThreadTs = replyThreadTs ?? threadCtx.messageTs;
```

This eliminates the redundant context resolution.

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

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

- Add isThreadReply to resolveSlackThreadTargets return value
- Remove duplicate call in dispatch.ts
- Addresses greptile review feedback with cleaner DRY approach
@theoseo
Copy link
Contributor Author

theoseo commented Feb 17, 2026

Thanks for the review @greptile-apps!

You're right about the redundant resolveSlackThreadContext call. However, instead of duplicating the logic from resolveSlackThreadTargets in dispatch.ts, I've refactored resolveSlackThreadTargets itself to also return isThreadReply.

This approach:

  • Eliminates the redundant computation ✅
  • Keeps the logic in one place (DRY) ✅
  • Makes dispatch.ts simpler with a single call ✅

Changed in commit 34368fc.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 17, 2026

Thanks for the thoughtful refactor! Moving isThreadReply into resolveSlackThreadTargets is definitely cleaner.

One small suggestion: consider adding a JSDoc comment to resolveSlackThreadTargets documenting what isThreadReply means, since it's now part of a shared utility function:

/**
 * Resolves Slack thread context for a message.
 * 
 * @param isThreadReply - true if this is a genuine user reply in a thread,
 *                        false if thread_ts comes from a bot status message (e.g. typing indicator)
 */
export async function resolveSlackThreadTargets(

This will help future maintainers understand the distinction between "has thread_ts" vs "is an actual thread reply" without needing to trace through the Slack integration logic.

The refactor looks solid otherwise! 👍

Document return values including isThreadReply distinction between
genuine user thread replies vs bot status message thread context.
@theoseo
Copy link
Contributor Author

theoseo commented Feb 17, 2026

Good idea! Added JSDoc documentation in 69e7902. 📝

@vincentkoc
Copy link
Member

Thanks for the DM-specific fix and follow-up refactor.

I’m closing this in favor of #23799, which includes your incomingThreadTs handling changes and carries them together with the rest of the Slack replyToMode: "off" fixes on top of current main.

Your contribution remains part of the merged path. If I missed any behavior from this branch, tell me and I’ll reopen review right away.

@vincentkoc vincentkoc closed this Feb 22, 2026
@vincentkoc vincentkoc added dedupe:child Duplicate issue/PR child in dedupe cluster close:duplicate Closed as duplicate labels Feb 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: slack Channel integration: slack close:duplicate Closed as duplicate dedupe:child Duplicate issue/PR child in dedupe cluster size: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Slack DM threading ignores replyToMode: off — statusThreadTs fallback creates threads

3 participants