#23320: fix(slack): respect replyToMode when incomingThreadTs is auto-created
channel: slack
size: S
Cluster:
Slack Thread Management Fixes
# fix(slack): respect replyToMode when Slack auto-creates thread_ts
> **AI-assisted:** This PR was authored with assistance from an AI coding assistant. All changes were manually reviewed and tested locally before submission.
## What
Fixes a bug where `replyToMode: "off"` was ignored when `incomingThreadTs` exists, causing all bot replies to become thread replies even when configured to post to the main channel.
## Why
When Slack's "Agents & AI Apps" feature is enabled (or in certain bot interaction scenarios), Slack auto-creates a `thread_ts` for channel messages. The existing code in `createSlackReplyReferencePlanner()` unconditionally forced `replyToMode` to `"all"` whenever `incomingThreadTs` was present:
```ts
// Before (buggy)
const effectiveMode = params.incomingThreadTs ? "all" : params.replyToMode;
```
This meant users who configured `replyToModeByChatType.channel: "off"` expecting channel-level replies would instead get thread replies when this auto-threading occurred.
The fix distinguishes between:
- **Genuine thread context** (user wrote in an existing thread → `isThreadReply=true`) → stay in thread regardless of `replyToMode`
- **Auto-created thread_ts** (Slack agent feature / channel message) → respect the configured `replyToMode`
## Related Issues
- Fixes #5470
- Related: #10837 (Thread replies leak to main channel)
- Related: #16080 (Referenced by alternative fix PR #16113)
## Reproduction Steps
### Before this fix:
1. Enable Slack "Agents & AI Apps" feature in your workspace (or use a bot that triggers auto-thread creation)
2. Configure `replyToModeByChatType.channel: "off"` in OpenClaw config
3. Send a message in a channel (not in a thread)
4. Observe that the bot replies in a thread instead of the main channel
### Expected behavior:
With `replyToMode: "off"`, the bot should reply to the main channel unless the user explicitly sent their message within an existing thread.
## Changes
1. **`src/slack/monitor/replies.ts`**: Modified `createSlackReplyReferencePlanner()` to accept `isThreadReply` parameter and use it to determine effective mode instead of just checking `incomingThreadTs`
2. **`src/slack/monitor/message-handler/dispatch.ts`**: Updated to pass `isThreadReply` context from `resolveSlackThreadContext()`
3. **`src/slack/threading.ts`**: Exposed `isThreadReply` in return value
4. **Tests**: Added/updated unit tests for the new behavior
## Test Plan
### Manual Testing
#### Scenario 1: Channel Message with Auto-Created Thread (Bug Fix)
- [ ] Configure `replyToModeByChatType.channel: "off"`
- [ ] Enable Slack "Agents & AI Apps" feature
- [ ] Send a message in a channel (not in a thread)
- [ ] **Expected**: Bot replies in main channel, not in auto-created thread
#### Scenario 2: Genuine Thread Reply (Preserved Behavior)
- [ ] Configure `replyToModeByChatType.channel: "off"`
- [ ] Start a thread by clicking "Reply in thread" on any message
- [ ] Send message within that thread
- [ ] **Expected**: Bot replies within the thread (genuine thread context respected)
#### Scenario 3: DM with replyToMode
- [ ] Configure `replyToModeByChatType.im: "off"` (or any value)
- [ ] Send DM to bot
- [ ] **Expected**: Bot respects configured mode for IM type
#### Scenario 4: replyToMode: "all" (Preserved Behavior)
- [ ] Configure `replyToModeByChatType.channel: "all"`
- [ ] Send message in channel (not thread)
- [ ] **Expected**: Bot replies in thread (existing behavior preserved)
#### Scenario 5: Mixed Chat Types
- [ ] Configure different modes for different chat types:
```yaml
replyToModeByChatType:
channel: "off"
group: "all"
im: "off"
```
- [ ] Test in channel, group DM, and 1:1 DM
- [ ] **Expected**: Each chat type respects its own configuration
### Automated Tests
- [ ] `pnpm test -- src/slack/monitor/replies.test.ts` passes
- [ ] New test cases added:
- `should respect replyToMode="off" when incomingThreadTs exists but isThreadReply=false`
- `should force thread reply when isThreadReply=true regardless of replyToMode`
- `should handle auto-created thread_ts from Slack Agents feature`
- [ ] Existing test cases still pass (no regressions)
### Edge Cases
- [ ] **Empty thread_ts**: Message with no thread context works correctly
- [ ] **Nested threads**: Thread within a thread handles correctly
- [ ] **Message edits**: Edited messages maintain correct reply context
- [ ] **Bot mentions**: @bot mentions in channel vs thread behave correctly
- [ ] **Channel-wide replyToMode** (deprecated but supported): Still works as fallback
### Build & Check
```bash
pnpm build && pnpm check && pnpm test
```
- [ ] Build completes without errors
- [ ] Type checking passes (`pnpm check`)
- [ ] All tests pass (`pnpm test`)
- [ ] Lint passes (`pnpm lint` if applicable)
## Comparison with PR #16113
**PR #16113** (by @zerone0x) addresses a similar issue but at the `deliverReplies()` level, focusing on inline directive tags (e.g., `@openclaw reply-to=channel`). This approach:
- Only fixes message delivery, not the planning phase
- Doesn't address the root cause in `createSlackReplyReferencePlanner()`
- Has been stale for some time
**Our approach** is more comprehensive:
- Fixes the **planner level** (`createSlackReplyReferencePlanner()`) where reply mode decisions are made
- Also ensures `deliverReplies()` correctly handles the `replyToId` parameter
- Handles both config-based and inline directive-based scenarios
- Includes proper test coverage for the planner logic
## Checklist
- [x] I have read the [CONTRIBUTING.md](../CONTRIBUTING.md) guide
- [x] My PR focuses on one thing (respecting replyToMode with auto-created threads)
- [x] I have tested my changes locally
- [x] I have added/updated tests for my changes
- [x] Build, check, and tests all pass
- [x] Marked as AI-assisted with testing details
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
fixes `replyToMode: "off"` being ignored when Slack auto-creates `thread_ts` for channel messages (Agents & AI Apps feature)
**Key changes:**
- adds `isThreadReply` flag to distinguish genuine thread replies from auto-created `thread_ts`
- `isThreadReply` is true when `thread_ts !== ts` OR `parent_user_id` is present
- updates `createSlackReplyReferencePlanner` to respect configured `replyToMode` for auto-created threads
- adds test coverage for auto-created thread scenarios
**Issues found:**
- logic bug in `resolveSlackThreadTargets` (src/slack/threading.ts:45-49) - doesn't handle `replyToMode: "first"` correctly for auto-created threads
- missing test coverage for "first" mode with auto-created threads
<h3>Confidence Score: 3/5</h3>
- safe to merge after fixing the logic bug in `resolveSlackThreadTargets`
- the PR correctly fixes the core issue in `createSlackReplyReferencePlanner`, but introduces a logic bug in `resolveSlackThreadTargets` that doesn't properly handle `replyToMode: "first"` with auto-created threads - this needs to be fixed before merge
- src/slack/threading.ts requires fixing the ternary logic on lines 45-49 to handle "first" mode correctly
<sub>Last reviewed commit: f158d38</sub>
<!-- greptile_other_comments_section -->
<sub>(5/5) You can turn off certain types of comments like style [here](https://app.greptile.com/review/github)!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#20406: fix(slack): respect replyToMode when computing statusThreadTs in DMs
by QuinnYates · 2026-02-18
88.0%
#19083: Slack: preserve per-thread context and consistent thread replies
by jkimbo · 2026-02-17
87.4%
#23513: fix(slack): respect replyToMode=off for inline directive reply tags
by dorukardahan · 2026-02-22
87.0%
#23799: fix(slack): finalize replyToMode off threading behavior
by vincentkoc · 2026-02-22
86.1%
#20389: fix(slack): inject thread history on first thread turn, not only on...
by lafawnduh1966 · 2026-02-18
85.0%
#12244: fix(slack): preserve thread context for DM thread replies
by junhoyeo · 2026-02-09
83.1%
#19403: feat(slack): add dm.threadSession option for per-message thread ses...
by Vasiliy-Bondarenko · 2026-02-17
82.2%
#4749: fix: handle string thread IDs in queue drain for Slack
by nvonpentz · 2026-01-30
82.2%
#19419: feat(slack): track thread participation for implicit mentions
by Utkarshbhimte · 2026-02-17
82.1%
#22485: fix(slack): use threadId from delivery context as threadTs fallback...
by dorukardahan · 2026-02-21
82.0%