Skip to content

Commit 2ca2221

Browse files
zozo123claude
andcommitted
fix: scope messageThreadId override to DM assistant threads only
The previous fix unconditionally set messageThreadId when hasThreadTs was true, which affected channel/group messages where thread_ts == ts (auto- created top-level threads). This caused buildSlackThreadingToolContext to force replyToMode to "all" in non-DM contexts, unexpectedly routing tool and subagent outputs into threads. Now the override only applies when channel_type is "im", correctly limiting it to the Slack Agents & Assistants DM root-message scenario. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2002d1e commit 2ca2221

2 files changed

Lines changed: 35 additions & 7 deletions

File tree

extensions/slack/src/threading.test.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,26 +88,48 @@ describe("resolveSlackThreadTargets", () => {
8888
expect(context.replyToId).toBe("123");
8989
});
9090

91-
it("sets messageThreadId for thread-root messages regardless of replyToMode", () => {
91+
it("sets messageThreadId for DM assistant thread-root messages regardless of replyToMode", () => {
9292
for (const replyToMode of ["off", "first", "batched"] as const) {
9393
const context = resolveSlackThreadContext({
9494
replyToMode,
9595
message: {
9696
type: "message",
97-
channel: "C1",
97+
channel: "D1",
98+
channel_type: "im",
9899
ts: "123",
99100
thread_ts: "123",
100101
},
101102
});
102103

103104
expect(context.isThreadReply).toBe(false);
104-
// thread_ts == ts: Agents & Assistants DM root — preserve thread context
105-
// so tool calls (subagent results) thread correctly regardless of mode.
105+
// thread_ts == ts in a DM: Agents & Assistants root — preserve thread
106+
// context so tool calls (subagent results) thread correctly.
106107
expect(context.messageThreadId).toBe("123");
107108
expect(context.replyToId).toBe("123");
108109
}
109110
});
110111

112+
it("does not set messageThreadId for channel thread-root messages with non-all replyToMode", () => {
113+
for (const replyToMode of ["off", "first", "batched"] as const) {
114+
const context = resolveSlackThreadContext({
115+
replyToMode,
116+
message: {
117+
type: "message",
118+
channel: "C1",
119+
channel_type: "channel",
120+
ts: "123",
121+
thread_ts: "123",
122+
},
123+
});
124+
125+
expect(context.isThreadReply).toBe(false);
126+
// thread_ts == ts in a channel: auto-created top-level thread_ts should
127+
// NOT force threaded mode — only DM assistant threads get the override.
128+
expect(context.messageThreadId).toBeUndefined();
129+
expect(context.replyToId).toBe("123");
130+
}
131+
});
132+
111133
it("prefers thread_ts as messageThreadId for replies", () => {
112134
const context = resolveSlackThreadContext({
113135
replyToMode: "off",

extensions/slack/src/threading.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,21 @@ export function resolveSlackThreadContext(params: {
2020
const isThreadReply =
2121
hasThreadTs && (incomingThreadTs !== messageTs || Boolean(params.message.parent_user_id));
2222
const replyToId = incomingThreadTs ?? messageTs;
23-
// Preserve thread context for thread replies AND for thread-root messages
24-
// (e.g. Slack Agents & Assistants DMs where thread_ts == ts on the initial
23+
// Preserve thread context for DM assistant thread-root messages
24+
// (Slack Agents & Assistants DMs where thread_ts == ts on the initial
2525
// message). Without this, tool calls that run during the same turn (subagent
2626
// results) lose the thread identifier after the first reply because
2727
// hasRepliedRef gets marked true, causing subsequent sendMessage calls to fall
2828
// through to the top-level channel.
29+
// Only apply for DM (im) channels — in channels/groups, an auto-created
30+
// thread_ts == ts must not force threaded mode, since downstream
31+
// buildSlackThreadingToolContext treats any MessageThreadId as an explicit
32+
// thread target and overrides replyToMode to "all".
33+
const isDmAssistantThread =
34+
hasThreadTs && !isThreadReply && params.message.channel_type === "im";
2935
const messageThreadId = isThreadReply
3036
? incomingThreadTs
31-
: hasThreadTs
37+
: isDmAssistantThread
3238
? incomingThreadTs
3339
: params.replyToMode === "all"
3440
? messageTs

0 commit comments

Comments
 (0)