#19403: feat(slack): add dm.threadSession option for per-message thread sessions 🤖
docs
channel: slack
gateway
size: S
Cluster:
Slack Thread Management Improvements
## Summary
- **Problem:** All top-level Slack DMs route to the shared main session (`agent:main:main`), causing context cross-contamination between unrelated conversations
- **Why it matters:** Users expect each DM conversation to be independent; instead, the bot "remembers" topics from completely unrelated messages because they share one session
- **What changed:** New `channels.slack.dm.threadSession` config option. When `true`, each top-level DM creates its own thread session (`agent:main:main:thread:<ts>`), and the bot replies as a Slack thread. Follow-ups in that thread stay in the same isolated session.
- **What did NOT change (scope boundary):** Channel messages, group DMs, existing `dmScope` routing, `replyToMode` semantics, thread reply behavior. Default is `false` — zero behavior change for existing users.
## Change Type (select all)
- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [x] Docs
- [ ] Security hardening
- [ ] Chore/infra
## Scope (select all touched areas)
- [x] Gateway / orchestration
- [ ] Skills / tool execution
- [ ] Auth / tokens
- [ ] Memory / storage
- [x] Integrations
- [ ] API / contracts
- [ ] UI / DX
- [ ] CI/CD / infra
## Linked Issue/PR
- Closes N/A (no existing issue found)
- Related: session routing, Slack threading
## User-visible / Behavior Changes
- New config option: `channels.slack.dm.threadSession` (boolean, default `false`)
- When enabled with `replyToMode` `"all"` or `"first"` for DMs: each new top-level DM gets a dedicated thread session; bot replies as a Slack thread
- No change when disabled or when `replyToMode` is `"off"`
## Security Impact (required)
- New permissions/capabilities? `No`
- Secrets/tokens handling changed? `No`
- New/changed network calls? `No`
- Command/tool execution surface changed? `No`
- Data access scope changed? `No`
## Repro + Verification
### Environment
- OS: Windows 11 ARM (Parallels on macOS M1)
- Runtime/container: Node.js 22.22.0
- Model/provider: Anthropic Claude Opus 4.6
- Integration/channel: Slack (Socket Mode)
- Relevant config:
```json
{
"channels": {
"slack": {
"dm": {
"replyToMode": "all",
"threadSession": true
}
}
}
}
```
### Steps
1. Enable `dm.threadSession: true` and `dm.replyToMode: "all"` in config
2. Send a top-level DM to the bot: "мы с тобой рыбу обсуждали в течение этой сессии?"
3. Observe the bot replies as a Slack thread with "Нет, рыбу мы не обсуждали. Это первое сообщение в этой сессии."
4. Send another unrelated top-level DM — it creates a separate thread with its own session
### Expected
- Each top-level DM creates a new isolated thread session
- No context leaks between threads
### Actual
- ✅ Matches expected behavior
## Evidence
- [x] Failing test/log before + passing after
- [ ] Trace/log snippets
- [ ] Screenshot/recording
- [ ] Perf numbers
14 new unit tests covering: thread session routing for top-level DMs, opt-out when `false`/missing, thread reply behavior unchanged, `replyToMode` gating, `IsFirstThreadTurn` flag, `parentSessionKey` inheritance.
Manual verification: live Slack DM confirmed isolated session — bot responded with no prior context.
## Human Verification (required)
- **Verified scenarios:** Top-level DM creates thread session; follow-up replies stay in same thread; `threadSession: false` preserves old behavior; different `replyToMode` values
- **Edge cases checked:** Thread replies (should not double-promote); `replyToMode: "off"` (should not activate); missing `dm` config block
- **What you did not verify:** Multi-account Slack setups; MPIM (group DM) behavior with this flag; high-volume concurrent DMs
## Compatibility / Migration
- Backward compatible? `Yes` — default `false`, no behavior change
- Config/env changes? `Yes` — new optional field `channels.slack.dm.threadSession`
- Migration needed? `No`
## Failure Recovery (if this breaks)
- **How to disable/revert:** Set `channels.slack.dm.threadSession: false` (or remove the key) and restart gateway
- **Files/config to restore:** `openclaw.json` — remove `threadSession` from `channels.slack.dm`
- **Known bad symptoms:** DMs not creating threads when expected; or all DMs routing to main session (revert to pre-feature behavior, which is harmless)
## Risks and Mitigations
- **Risk:** If `replyToMode` is not set to `"all"` or `"first"`, enabling `threadSession` has no effect (silent no-op, could confuse users)
- **Mitigation:** Documented the requirement; code explicitly gates on `replyToMode` value
---
> 🤖 This PR was developed with AI assistance.
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR adds a `dm.threadSession` config option for Slack that routes each new top-level DM to its own isolated thread session, preventing context bleed between unrelated conversations. The implementation is well-scoped, backward-compatible (default `false`), and comes with 14 unit tests.
**Key changes:**
- `SlackDmConfig` in `types.slack.ts` and the Zod schema gain an optional `threadSession: boolean` field.
- `prepare.ts` computes `shouldPromoteDmToThread` and uses `threadContext.messageTs` as the thread ID, routing the message to `agent:...:thread:<ts>`.
- `IsFirstThreadTurn` is unconditionally set to `true` for promoted DMs (each has a unique `ts`, so it is always the first turn — acceptable but undocumented).
- Docs in `slack.md` and `configuration-reference.md` are updated correctly.
**Issues found:**
- **Logic (dispatch mismatch):** `shouldPromoteDmToThread` is gated on `dmReplyToMode` (which respects `replyToModeByChatType.direct` and `dm.replyToMode`), but the dispatch layer in `dispatch.ts` drives Slack thread replies using `ctx.replyToMode` (the global setting only). If a user sets `replyToMode: "off"` globally and enables DM threading only via `replyToModeByChatType.direct: "all"`, the session is promoted to a thread session but the Slack reply is sent to the main channel — creating an orphaned session. The common path (`replyToMode: "all"` globally, which is also the default) works correctly.
- **Style:** `IsFirstThreadTurn` for promoted DMs does not check for a pre-existing session the way the `isThreadReply` path does.
- **Style:** A type cast in the test helper silently drops `"first"` from the `replyToMode` union passed to the context.
<h3>Confidence Score: 3/5</h3>
- Safe to merge for the common configuration (global `replyToMode: "all"`), but has a logic gap when `replyToModeByChatType.direct` or `dm.replyToMode` is used as the sole DM threading override.
- The feature works correctly for the documented and most common configuration (`replyToMode: "all"` globally + `dm.threadSession: true`). However, there is a real logic mismatch: `shouldPromoteDmToThread` respects per-chat-type `replyToMode` overrides, but `dispatch.ts` uses only the global `ctx.replyToMode` to decide whether to actually reply as a Slack thread. A user who sets `replyToMode: "off"` globally and `replyToModeByChatType.direct: "all"` will get a thread session in storage but replies landing in the main channel — an inconsistent and confusing experience. Tests cover session-key routing but not the dispatch-layer threading behavior.
- `src/slack/monitor/message-handler/prepare.ts` (lines 209-214) for the dispatch mismatch; `src/slack/monitor/message-handler/dispatch.ts` for where the fix would need to land.
<sub>Last reviewed commit: 6b57f64</sub>
<!-- greptile_other_comments_section -->
<sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#19083: Slack: preserve per-thread context and consistent thread replies
by jkimbo · 2026-02-17
84.7%
#15969: fix: per-thread session isolation for Slack DMs when replyToMode is...
by neeravmakwana · 2026-02-14
84.1%
#12244: fix(slack): preserve thread context for DM thread replies
by junhoyeo · 2026-02-09
83.9%
#10686: fix(slack): use thread-level sessions for channels to prevent conte...
by pablohrcarvalho · 2026-02-06
83.2%
#22433: Slack: fix thread context loss after session reset
by stgarrity · 2026-02-21
82.9%
#20406: fix(slack): respect replyToMode when computing statusThreadTs in DMs
by QuinnYates · 2026-02-18
82.7%
#23320: fix(slack): respect replyToMode when incomingThreadTs is auto-created
by dorukardahan · 2026-02-22
82.2%
#19274: feat(mattermost): enable threaded replies in channels
by rockinyp · 2026-02-17
82.2%
#19419: feat(slack): track thread participation for implicit mentions
by Utkarshbhimte · 2026-02-17
81.3%
#16223: feat(slack): sticky thread routing — bypass @mention in active threads
by iamfuntime · 2026-02-14
81.2%