From e2e10b3da49dcd75263a48c2198908d7027cf946 Mon Sep 17 00:00:00 2001 From: David Murray Date: Mon, 23 Feb 2026 19:22:45 -0800 Subject: [PATCH] fix(slack): map threadId to replyToId for restart sentinel notifications (#24885) The restart sentinel wake path passes threadId to deliverOutboundPayloads, but Slack requires replyToId (mapped to thread_ts) for threading. The agent reply path already does this conversion but the sentinel path did not, causing post-restart notifications to land as top-level DMs. Fixes #17716 --- src/gateway/server-restart-sentinel.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/gateway/server-restart-sentinel.ts b/src/gateway/server-restart-sentinel.ts index e536193accd..454657d188d 100644 --- a/src/gateway/server-restart-sentinel.ts +++ b/src/gateway/server-restart-sentinel.ts @@ -76,13 +76,22 @@ export async function scheduleRestartSentinelWake(_params: { deps: CliDeps }) { sessionThreadId ?? (origin?.threadId != null ? String(origin.threadId) : undefined); + // Slack uses replyToId (thread_ts) for threading, not threadId. + // The reply path does this mapping but deliverOutboundPayloads does not, + // so we must convert here to ensure post-restart notifications land in + // the originating Slack thread. See #17716. + const isSlack = channel === "slack"; + const replyToId = isSlack && threadId != null && threadId !== "" ? String(threadId) : undefined; + const resolvedThreadId = isSlack ? undefined : threadId; + try { await deliverOutboundPayloads({ cfg, channel, to: resolved.to, accountId: origin?.accountId, - threadId, + replyToId, + threadId: resolvedThreadId, payloads: [{ text: message }], agentId: resolveSessionAgentId({ sessionKey, config: cfg }), bestEffort: true,