#15969: fix: per-thread session isolation for Slack DMs when replyToMode is "all"
channel: slack
stale
size: S
Cluster:
Slack Thread Management Fixes
## Summary
- When `replyToMode` is `"all"`, top-level Slack DMs now get their own session key (scoped by the message's `ts`), so each new message starts a fresh conversation instead of sharing context with all prior unrelated messages.
- Thread replies continue to share the same session as their parent message, so conversations within a thread remain coherent.
- No behavior change when `replyToMode` is not `"all"` — the existing shared session behavior is fully preserved.
## Problem
When `replyToMode` is set to `"all"` in Slack DM configuration, the bot replies in a thread under each message. Users naturally expect each thread to be an independent conversation. However, all top-level DMs currently share a single persistent session (`agent:<agentId>:main`), which causes:
1. Context leakage between unrelated conversations (e.g., asking about diapers once causes all subsequent queries to only return diapers)
2. The bot reusing stale answers from prior turns instead of doing fresh work
3. Unnecessary token usage from accumulating unrelated context
## Fix
In `src/slack/monitor/message-handler/prepare.ts`, the `threadId` passed to `resolveThreadSessionKeys` now uses the message's own `ts` as `threadId` for top-level messages when `replyToMode === "all"`:
\`\`\`typescript
threadId: isThreadReply
? threadTs
: ctx.replyToMode === "all"
? threadContext.messageTs
: undefined,
\`\`\`
This produces session keys like `agent:main:main:thread:1770865253.355799` for each top-level message. When a user replies in that thread, `thread_ts` equals the parent's `ts`, so both resolve to the same session key.
## Conflict resolution
Rebased onto latest `main` to resolve a merge conflict caused by upstream test refactoring:
- `prepare.inbound-contract.test.ts` was renamed to `prepare.test.ts` and consolidated with other test suites (`a7f6c956`, `8181f51d`, `aae290ee`, `2f67564c`)
- Rewrote our 3 new tests to use the shared helpers introduced by those refactors (`createThreadSlackCtx`, `createThreadAccount`, `prepareWithDefaultCtx`), consistent with the rest of the test suite
## Test plan
- [x] Added test: two separate top-level DMs with `replyToMode=all` produce different session keys
- [x] Added test: a top-level message and its thread reply share the same session key
- [x] Added test: `replyToMode=off` preserves the existing shared session behavior (no regression)
- [x] All existing tests pass (5,199 tests, 0 failures)
- [x] `pnpm build` passes
- [x] `pnpm check` passes (pre-existing TS error in `fast-tool-stubs.ts` unrelated to this PR)
- [x] `pnpm test` passes
AI-assisted: Yes (Cursor + Claude). Fully tested locally. I understand what the code does.
Most Similar PRs
#19403: feat(slack): add dm.threadSession option for per-message thread ses...
by Vasiliy-Bondarenko · 2026-02-17
84.1%
#12244: fix(slack): preserve thread context for DM thread replies
by junhoyeo · 2026-02-09
81.8%
#10686: fix(slack): use thread-level sessions for channels to prevent conte...
by pablohrcarvalho · 2026-02-06
81.0%
#23799: fix(slack): finalize replyToMode off threading behavior
by vincentkoc · 2026-02-22
79.9%
#19083: Slack: preserve per-thread context and consistent thread replies
by jkimbo · 2026-02-17
78.8%
#16186: fix(slack): thread history on subsequent turns, inbound meta contex...
by markshields-tl · 2026-02-14
78.6%
#20406: fix(slack): respect replyToMode when computing statusThreadTs in DMs
by QuinnYates · 2026-02-18
78.4%
#23320: fix(slack): respect replyToMode when incomingThreadTs is auto-created
by dorukardahan · 2026-02-22
77.9%
#23804: fix(slack): preserve string thread context in queue + DM route
by vincentkoc · 2026-02-22
77.8%
#22433: Slack: fix thread context loss after session reset
by stgarrity · 2026-02-21
77.0%