Skip to content

Commit 46da76e

Browse files
dbachelderclaudeTakhoffman
authored
fix(slack): honor replyToModeByChatType when ThreadLabel exists (openclaw#26251)
* fix(slack): honor direct replyToMode when thread label exists ThreadLabel is a session/conversation label, not a reliable indicator of an actual Slack thread reply. Using it to force replyToMode="all" overrides replyToModeByChatType.direct="off" in DMs. Switch to MessageThreadId which indicates a real thread target is available, preserving expected behavior: thread replies stay threaded, normal DMs respect the configured mode. Co-Authored-By: Claude Opus 4.6 <[email protected]> * Slack: add changelog for threading tool context fix --------- Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: Tak Hoffman <[email protected]>
1 parent a28a4b1 commit 46da76e

File tree

3 files changed

+50
-4
lines changed

3 files changed

+50
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ Docs: https://docs.openclaw.ai
243243
- Slack/Identity: thread agent outbound identity (`chat:write.customize` overrides) through the channel reply delivery path so per-agent username, icon URL, and icon emoji are applied to all Slack replies including media messages. (#27134) Thanks @hou-rong.
244244
- Slack/Threading: resolve `replyToMode` per incoming message using chat-type-aware account config (`replyToModeByChatType` and legacy `dm.replyToMode`) so DM/channel reply threading honors overrides instead of always using monitor startup defaults. (#24717) Thanks @dbachelder.
245245
- Slack/Threading: track bot participation in message threads (per account/channel/thread) so follow-up messages in those threads can be handled without requiring repeated @mentions, while preserving mention-gating behavior for unrelated threads. (#29165) Thanks @luijoc.
246+
- Slack/Threading: stop forcing tool-call reply mode to `all` based on `ThreadLabel` alone; now force thread reply mode only when an explicit thread target exists (`MessageThreadId`/`ReplyToId`), so DM `replyToModeByChatType.direct` overrides are honored outside real thread replies. (#26251) Thanks @dbachelder.
246247
- Agents/Subagents delivery: refactor subagent completion announce dispatch into an explicit queue/direct/fallback state machine, recover outbound channel-plugin resolution in cold/stale plugin-registry states across announce/message/gateway send paths, finalize cleanup bookkeeping when announce flow rejects, and treat Telegram sends without `message_id` as delivery failures (instead of false-success `"unknown"` IDs). (#26867, #25961, #26803, #25069, #26741) Thanks @SmithLabsLLC and @docaohieu2808.
247248
- Telegram/Webhook: pre-initialize webhook bots, switch webhook processing to callback-mode JSON handling, and preserve full near-limit payload reads under delayed handlers to prevent webhook request hangs and dropped updates. (#26156)
248249
- Slack/Session threads: prevent oversized parent-session inheritance from silently bricking new thread sessions, surface embedded context-overflow empty-result failures to users, and add configurable `session.parentForkMaxTokens` (default `100000`, `0` disables). (#26912) Thanks @markshields-tl.

src/slack/threading-tool-context.test.ts

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,20 +86,64 @@ describe("buildSlackThreadingToolContext", () => {
8686
expect(result.replyToMode).toBe("all");
8787
});
8888

89-
it("uses all mode when ThreadLabel is present", () => {
89+
it("uses all mode when MessageThreadId is present", () => {
9090
const cfg = {
9191
channels: {
92-
slack: { replyToMode: "off" },
92+
slack: {
93+
replyToMode: "all",
94+
replyToModeByChatType: { direct: "off" },
95+
},
9396
},
9497
} as OpenClawConfig;
9598
const result = buildSlackThreadingToolContext({
9699
cfg,
97100
accountId: null,
98-
context: { ChatType: "channel", ThreadLabel: "some-thread" },
101+
context: {
102+
ChatType: "direct",
103+
ThreadLabel: "thread-label",
104+
MessageThreadId: "1771999998.834199",
105+
},
99106
});
100107
expect(result.replyToMode).toBe("all");
101108
});
102109

110+
it("does not force all mode from ThreadLabel alone", () => {
111+
const cfg = {
112+
channels: {
113+
slack: {
114+
replyToMode: "all",
115+
replyToModeByChatType: { direct: "off" },
116+
},
117+
},
118+
} as OpenClawConfig;
119+
const result = buildSlackThreadingToolContext({
120+
cfg,
121+
accountId: null,
122+
context: {
123+
ChatType: "direct",
124+
ThreadLabel: "label-without-real-thread",
125+
},
126+
});
127+
expect(result.replyToMode).toBe("off");
128+
});
129+
130+
it("keeps configured channel behavior when not in a thread", () => {
131+
const cfg = {
132+
channels: {
133+
slack: {
134+
replyToMode: "off",
135+
replyToModeByChatType: { channel: "first" },
136+
},
137+
},
138+
} as OpenClawConfig;
139+
const result = buildSlackThreadingToolContext({
140+
cfg,
141+
accountId: null,
142+
context: { ChatType: "channel", ThreadLabel: "label-only" },
143+
});
144+
expect(result.replyToMode).toBe("first");
145+
});
146+
103147
it("defaults to off when no replyToMode is configured", () => {
104148
const result = buildSlackThreadingToolContext({
105149
cfg: emptyCfg,

src/slack/threading-tool-context.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ export function buildSlackThreadingToolContext(params: {
1616
accountId: params.accountId,
1717
});
1818
const configuredReplyToMode = resolveSlackReplyToMode(account, params.context.ChatType);
19-
const effectiveReplyToMode = params.context.ThreadLabel ? "all" : configuredReplyToMode;
19+
const hasExplicitThreadTarget = params.context.MessageThreadId != null;
20+
const effectiveReplyToMode = hasExplicitThreadTarget ? "all" : configuredReplyToMode;
2021
const threadId = params.context.MessageThreadId ?? params.context.ReplyToId;
2122
return {
2223
currentChannelId: params.context.To?.startsWith("channel:")

0 commit comments

Comments
 (0)