From e31764fed19f41e7a0b72c4d1163b48433d176ff Mon Sep 17 00:00:00 2001 From: spiceoogway Date: Fri, 30 Jan 2026 08:42:22 -0500 Subject: [PATCH] fix: prevent multi-channel response routing race condition (#4530) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem When multiple channels (WhatsApp, iMessage, Telegram, etc.) are active, responses intended for one channel could leak to another channel if messages arrived simultaneously. This created serious privacy concerns. **Root Cause:** All channels were updating the SAME main session key with their delivery context (channel, to, accountId). When messages arrived concurrently: 1. WhatsApp message arrives → updates session['agent:main:main'].lastChannel = 'whatsapp' 2. Agent starts processing (takes time) 3. iMessage message arrives → OVERWRITES session['agent:main:main'].lastChannel = 'imessage' 4. WhatsApp response delivers → reads session lastChannel = 'imessage' 5. Response goes to wrong channel! ## Solution Changed all channel monitors to use the channel-specific session key (route.sessionKey) instead of the shared main session key (route.mainSessionKey) when updating delivery context. Now each channel updates its own isolated session: - WhatsApp: session['agent:main:whatsapp:dm:+1234'] - iMessage: session['agent:main:imessage:dm:alice'] - Telegram: session['agent:main:telegram:dm:12345'] This prevents cross-channel state clobbering. ## Changes - src/discord/monitor/message-handler.process.ts - src/imessage/monitor/monitor-provider.ts - src/line/bot-message-context.ts - src/signal/monitor/event-handler.ts - src/slack/monitor/message-handler/dispatch.ts - src/slack/monitor/message-handler/prepare.ts - src/telegram/bot-message-context.ts - src/web/auto-reply/monitor/process-message.ts All changed from: sessionKey: route.mainSessionKey To: sessionKey: route.sessionKey ## Testing Manual testing needed: 1. Configure 2+ channels (e.g., WhatsApp + iMessage) 2. Send message to channel A 3. Immediately send message to channel B (within 100ms) 4. Verify responses go to correct channels Fixes #4530 --- src/discord/monitor/message-handler.process.ts | 2 +- src/imessage/monitor/monitor-provider.ts | 2 +- src/line/bot-message-context.ts | 4 ++-- src/signal/monitor/event-handler.ts | 2 +- src/slack/monitor/message-handler/dispatch.ts | 2 +- src/slack/monitor/message-handler/prepare.ts | 2 +- src/telegram/bot-message-context.ts | 2 +- src/web/auto-reply/monitor/process-message.ts | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/discord/monitor/message-handler.process.ts b/src/discord/monitor/message-handler.process.ts index 6d502be21..e9901d07e 100644 --- a/src/discord/monitor/message-handler.process.ts +++ b/src/discord/monitor/message-handler.process.ts @@ -302,7 +302,7 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext) ctx: ctxPayload, updateLastRoute: isDirectMessage ? { - sessionKey: route.mainSessionKey, + sessionKey: route.sessionKey, channel: "discord", to: `user:${author.id}`, accountId: route.accountId, diff --git a/src/imessage/monitor/monitor-provider.ts b/src/imessage/monitor/monitor-provider.ts index 491071699..05e46436d 100644 --- a/src/imessage/monitor/monitor-provider.ts +++ b/src/imessage/monitor/monitor-provider.ts @@ -510,7 +510,7 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P updateLastRoute: !isGroup && updateTarget ? { - sessionKey: route.mainSessionKey, + sessionKey: route.sessionKey, channel: "imessage", to: updateTarget, accountId: route.accountId, diff --git a/src/line/bot-message-context.ts b/src/line/bot-message-context.ts index 8a1c049d2..77b6264b5 100644 --- a/src/line/bot-message-context.ts +++ b/src/line/bot-message-context.ts @@ -282,7 +282,7 @@ export async function buildLineMessageContext(params: BuildLineMessageContextPar if (!isGroup) { await updateLastRoute({ storePath, - sessionKey: route.mainSessionKey, + sessionKey: route.sessionKey, deliveryContext: { channel: "line", to: userId ?? peerId, @@ -432,7 +432,7 @@ export async function buildLinePostbackContext(params: { if (!isGroup) { await updateLastRoute({ storePath, - sessionKey: route.mainSessionKey, + sessionKey: route.sessionKey, deliveryContext: { channel: "line", to: userId ?? peerId, diff --git a/src/signal/monitor/event-handler.ts b/src/signal/monitor/event-handler.ts index 72195ff78..eb45e1dd2 100644 --- a/src/signal/monitor/event-handler.ts +++ b/src/signal/monitor/event-handler.ts @@ -156,7 +156,7 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) { ctx: ctxPayload, updateLastRoute: !entry.isGroup ? { - sessionKey: route.mainSessionKey, + sessionKey: route.sessionKey, channel: "signal", to: entry.senderRecipient, accountId: route.accountId, diff --git a/src/slack/monitor/message-handler/dispatch.ts b/src/slack/monitor/message-handler/dispatch.ts index 38b69f049..a0c819638 100644 --- a/src/slack/monitor/message-handler/dispatch.ts +++ b/src/slack/monitor/message-handler/dispatch.ts @@ -27,7 +27,7 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag }); await updateLastRoute({ storePath, - sessionKey: route.mainSessionKey, + sessionKey: route.sessionKey, deliveryContext: { channel: "slack", to: `user:${message.user}`, diff --git a/src/slack/monitor/message-handler/prepare.ts b/src/slack/monitor/message-handler/prepare.ts index 8a2a9e111..02fc19cae 100644 --- a/src/slack/monitor/message-handler/prepare.ts +++ b/src/slack/monitor/message-handler/prepare.ts @@ -533,7 +533,7 @@ export async function prepareSlackMessage(params: { ctx: ctxPayload, updateLastRoute: isDirectMessage ? { - sessionKey: route.mainSessionKey, + sessionKey: route.sessionKey, channel: "slack", to: `user:${message.user}`, accountId: route.accountId, diff --git a/src/telegram/bot-message-context.ts b/src/telegram/bot-message-context.ts index 9696e4f1b..41188df50 100644 --- a/src/telegram/bot-message-context.ts +++ b/src/telegram/bot-message-context.ts @@ -617,7 +617,7 @@ export const buildTelegramMessageContext = async ({ ctx: ctxPayload, updateLastRoute: !isGroup ? { - sessionKey: route.mainSessionKey, + sessionKey: route.sessionKey, channel: "telegram", to: String(chatId), accountId: route.accountId, diff --git a/src/web/auto-reply/monitor/process-message.ts b/src/web/auto-reply/monitor/process-message.ts index 27b638cfb..af8feaa05 100644 --- a/src/web/auto-reply/monitor/process-message.ts +++ b/src/web/auto-reply/monitor/process-message.ts @@ -295,7 +295,7 @@ export async function processMessage(params: { cfg: params.cfg, backgroundTasks: params.backgroundTasks, storeAgentId: params.route.agentId, - sessionKey: params.route.mainSessionKey, + sessionKey: params.route.sessionKey, channel: "whatsapp", to: dmRouteTarget, accountId: params.route.accountId,