-
-
Notifications
You must be signed in to change notification settings - Fork 69.2k
[Bug]: chat.send inherits OriginatingChannel from session delivery context, causing duplicate delivery in dmScope=main sessions #33619
Description
Bug type
Regression (worked before, now fails)
Summary
Commit 050e928 (#29328, fix for #31573) changed chat.send to inherit OriginatingChannel from the session's deliveryContext/lastChannel instead of always using INTERNAL_MESSAGE_CHANNEL. This fixes Feishu per-channel session routing but causes duplicate message delivery for users with dmScope: "main" who share a single session across multiple channels.
When a user chats on Discord (or Telegram, Signal, etc.) and then switches to the WebChat UI, responses are now sent to both the external channel and the WebUI, because the shared main session's lastChannel is inherited as OriginatingChannel, triggering shouldRouteToOriginating = true in dispatch-from-config.ts.
Steps to reproduce
- Configure
dmScope: "main"(shared main session across all DM channels) - Send a message via Discord (or any external channel)
- Open the WebChat UI and send a message on the same main session
- Observe that the bot's response is delivered to both Discord and WebChat
- WebChat shows 2 messages (one from the webchat dispatcher, one mirrored from the Discord delivery)
Expected behavior
When sending from WebChat, responses should only appear on WebChat. The external channel's lastChannel should not cause cross-channel delivery from the web UI.
Actual behavior
chat.ts reads session.deliveryContext.channel (e.g. "discord") and sets OriginatingChannel: "discord" on the dispatch context. dispatch-from-config.ts sees OriginatingChannel = "discord" with Provider = "webchat", so shouldRouteToOriginating = true, and the response is routed to Discord via routeReply in addition to the webchat dispatcher.
Root cause
In src/gateway/server-methods/chat.ts (lines added by 050e928):
const routeChannelCandidate = normalizeMessageChannel(
entry?.deliveryContext?.channel ?? entry?.lastChannel,
);
// ...
const originatingChannel = hasDeliverableRoute
? routeChannelCandidate
: INTERNAL_MESSAGE_CHANNEL;This unconditionally inherits the session's delivery route. For per-channel sessions (Feishu, Telegram), this is correct — the user explicitly selected that session in the sidebar. But for dmScope=main shared sessions, the lastChannel reflects whichever external channel was used most recently, not the user's current intent.
Possible fix
The hasDeliverableRoute check should distinguish between per-channel sessions (where inheriting makes sense) and shared main sessions (where it doesn't). For example, only inherit when the session key contains a channel-specific segment (e.g. agent:main:feishu:direct:...) rather than the bare agent:main:main.
OpenClaw version
2026.3.3 (includes 050e928)
Operating system
Linux
Install method
Source (npm link)