Skip to content

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

@stevievee

Description

@stevievee

Bug Description

When replyToMode is set to "off" for Slack direct messages (via replyToModeByChatType.direct: "off"), the bot still replies in threads. This is a regression introduced in 2026.2.14.

Configuration

{
  "channels": {
    "slack": {
      "replyToMode": "off",
      "replyToModeByChatType": {
        "direct": "off",
        "group": "all",
        "channel": "all"
      }
    }
  }
}

Expected Behavior

Bot replies appear in the main DM flow, not in threads.

Actual Behavior

Every bot reply appears as a thread reply to the user's message.

Root Cause Analysis

Traced through the source and identified two issues working together:

1. statusThreadTs always falls back to messageTs (reply-CYMZTXlH.js)

In resolveSlackThreadTargets:

const replyThreadTs = incomingThreadTs ?? (params.replyToMode === "all" ? messageTs : void 0);
return {
    replyThreadTs,
    statusThreadTs: replyThreadTs ?? messageTs  // <-- always has a value
};

When replyToMode is "off" and there's no incoming thread:

  • replyThreadTs = undefined
  • statusThreadTs = undefined ?? messageTs = messageTs

This means the "is typing..." status indicator is posted as a thread reply to the user's message, creating a Slack thread even when threading is disabled:

await ctx.setSlackThreadStatus({
    channelId: message.channel,
    threadTs: statusThreadTs,  // <-- messageTs, creates thread
    status: "is typing..."
});

2. createSlackReplyReferencePlanner overrides replyToMode to "all" (reply-CYMZTXlH.js)

function createSlackReplyReferencePlanner(params) {
    return createReplyReferencePlanner({
        replyToMode: params.incomingThreadTs ? "all" : params.replyToMode,
        // ...
    });
}

Once the typing indicator creates a thread (from issue 1), subsequent messages from the user may arrive with incomingThreadTs set. This function then hardcodes replyToMode: "all", completely ignoring the user's config.

The cycle:

  1. User sends message in main DM
  2. statusThreadTs = messageTs → typing indicator creates a thread
  3. Reply delivery respects replyToMode: "off" for the reply text, BUT the thread already exists from the typing indicator
  4. On subsequent messages, incomingThreadTs is set → replyToMode forced to "all" → all replies thread

Suggested Fix

Fix 1: Don't fall back to messageTs for statusThreadTs when not threading:

statusThreadTs: replyThreadTs  // remove ?? messageTs fallback

Fix 2: Respect configured replyToMode even when an incoming thread exists:

replyToMode: params.replyToMode  // don't override to "all"

Additional note

allowExplicitReplyTagsWhenOff (renamed from allowTagsWhenOff in #16189) is hardcoded to true in the Slack plugin dock (plugin-sdk/index.js). This means explicit [[reply_to_*]] tags from the model also create threads when replyToMode is "off". This should ideally be user-configurable via openclaw.json, but the config validator rejects threading as an unrecognized key on both channels.slack and plugins.entries.slack.

Environment

  • OpenClaw version: 2026.2.14 (c1feda1)
  • Channel: Slack (socket mode, plugin)
  • OS: Ubuntu on EC2 (t3a.medium)
  • Node: 22.22.0

Workaround

None found that persists. The threading behavior is in the gateway code, not user-configurable. Patching the dist files directly doesn't reliably take effect (multiple module copies).

Metadata

Metadata

Assignees

No one assigned

    Labels

    dedupe:parentPrimary canonical item in dedupe cluster

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions