Summary
When Slack's Agents & Assistants feature is enabled, DM messages arrive with thread_ts (assistant thread context). The main agent's first response correctly uses this thread_ts, but subagent results lose the thread context — appearing as new top-level messages or, worse, contaminating unrelated threads when multiple conversations are active simultaneously.
Root Causes
1. replyToMode: "first" consumes threadTs on first reply
createReplyReferencePlanner in reply-threading marks hasReplied=true after the first response. Subsequent responses (subagent results) get undefined threadTs. Even with replyToMode: "all", subagent result turns create new replyPlans without the original incomingThreadTs.
2. removeThreadFromDeliveryContext strips stored threadId
In store session management (store-*.js), when a subagent result updates the session without an explicit threadId, removeThreadFromDeliveryContext() deletes the previously stored thread context from the delivery context.
3. Single-value thread cache causes cross-thread contamination
When using a per-channel cache to preserve thread_ts across turns, concurrent DM conversations (e.g., a deploy request in thread A and a market report in thread B) overwrite each other's cached thread_ts. Results from thread B's subagent end up as replies in thread A.
Proposed Fix
The thread_ts from the originating DM session should be preserved per-session across subagent result turns:
- Cache
thread_ts per sessionKey (not per channel) on inbound in provider
- Restore per-session
thread_ts in postSlackMessageBestEffort when outbound threadTs is missing for DM channels
- Remove
removeThreadFromDeliveryContext call from session delivery context merging — preserve existing threadId when no explicit override is provided
- Alternatively, extend
createSlackReplyDeliveryPlan to accept a fallback threadTs from the session store
Reproduction
- Enable Agents & Assistants in Slack App settings
- DM the bot with a task requiring subagent delegation (e.g., deploy via Saya)
- First response (delegation status) appears in the assistant thread ✓
- Subagent result appears as a new top-level message ✗
Cross-thread contamination:
- Open two assistant threads simultaneously in the same DM
- Request different tasks in each (e.g., deploy in thread A, market report in thread B)
- Thread B's result appears in thread A ✗
Affected Versions
v2026.4.5, v2026.4.7, v2026.4.8 (all npm versions when Slack Agents & Assistants is enabled)
Workaround
Runtime patches on compiled JS files:
provider-*.js: Cache inbound thread_ts per sessionKey via globalThis, restore in deliverNormally fallback
send-*.js: In postSlackMessageBestEffort, check globalThis cache when threadTs is undefined for DM channels (channelId.startsWith("D"))
store-*.js: Replace removeThreadFromDeliveryContext(deliveryContextFromSession(existing)) with deliveryContextFromSession(existing) to preserve threadId
Environment
macOS, local gateway mode (non-Docker), Slack Socket Mode
Summary
When Slack's Agents & Assistants feature is enabled, DM messages arrive with
thread_ts(assistant thread context). The main agent's first response correctly uses thisthread_ts, but subagent results lose the thread context — appearing as new top-level messages or, worse, contaminating unrelated threads when multiple conversations are active simultaneously.Root Causes
1.
replyToMode: "first"consumes threadTs on first replycreateReplyReferencePlannerinreply-threadingmarkshasReplied=trueafter the first response. Subsequent responses (subagent results) getundefinedthreadTs. Even withreplyToMode: "all", subagent result turns create new replyPlans without the originalincomingThreadTs.2.
removeThreadFromDeliveryContextstrips stored threadIdIn
storesession management (store-*.js), when a subagent result updates the session without an explicitthreadId,removeThreadFromDeliveryContext()deletes the previously stored thread context from the delivery context.3. Single-value thread cache causes cross-thread contamination
When using a per-channel cache to preserve thread_ts across turns, concurrent DM conversations (e.g., a deploy request in thread A and a market report in thread B) overwrite each other's cached thread_ts. Results from thread B's subagent end up as replies in thread A.
Proposed Fix
The
thread_tsfrom the originating DM session should be preserved per-session across subagent result turns:thread_tsper sessionKey (not per channel) on inbound inproviderthread_tsinpostSlackMessageBestEffortwhen outboundthreadTsis missing for DM channelsremoveThreadFromDeliveryContextcall from session delivery context merging — preserve existing threadId when no explicit override is providedcreateSlackReplyDeliveryPlanto accept a fallbackthreadTsfrom the session storeReproduction
Cross-thread contamination:
Affected Versions
v2026.4.5, v2026.4.7, v2026.4.8 (all npm versions when Slack Agents & Assistants is enabled)
Workaround
Runtime patches on compiled JS files:
provider-*.js: Cache inboundthread_tsper sessionKey viaglobalThis, restore indeliverNormallyfallbacksend-*.js: InpostSlackMessageBestEffort, checkglobalThiscache whenthreadTsis undefined for DM channels (channelId.startsWith("D"))store-*.js: ReplaceremoveThreadFromDeliveryContext(deliveryContextFromSession(existing))withdeliveryContextFromSession(existing)to preserve threadIdEnvironment
macOS, local gateway mode (non-Docker), Slack Socket Mode