Skip to content

sessions_send hardcodes channel="webchat" causing silent misroute when target sessionKey is bound to a different channel #93255

Description

@drgd4jkfpm-afk

Summary

sessions_send silently misroutes messages when the target sessionKey is bound to a channel other than webchat. The tool reports status=accepted and echoes the requested sessionKey back to the caller, but the message lands in a different sibling session for the same target agent — one that happens to be bound to webchat.

Root cause: sessions_send hardcodes channel: INTERNAL_MESSAGE_CHANNEL (= "webchat") on the gateway agent call regardless of the target session's actual channel. When the gateway resolves the inbound by (sessionKey, channel) tuple, the channel value wins over the explicit, fully-qualified sessionKey.

User-facing harm

  • Cross-agent messages targeting Telegram-/Slack-/Signal-bound sessions are silently delivered to the target agent's webchat session.
  • The caller sees status=accepted with the correct sessionKey echoed → no way to detect the misroute without manually checking session transcripts.
  • Downstream agents may interpret the inbound as a new task and duplicate work that the correct session already handled — exactly what was caught in our INC-021 / INC-022 post-mortem tonight.

Worked example (real receipts)

Two consecutive sends from two parallel sessions of the same agent, same target agent, same downstream worker (b2184c98 is the correct Telegram-bound analyst session, 57c030a5 is the analyst's webchat session for the same user).

Test Source sessionKey (chan) Target sessionKey passed Actual delivery target Outcome
A agent:main:telegram:default:direct:719408963 (telegram) agent:analyst:main (alias) b2184c98 (telegram) ✅ correct
B agent:main:telegram:default:direct:719408963 (webchat) agent:analyst:telegram:analyst:direct:719408963 (explicit) 57c030a5 (webchat) ❌ wrong

Both calls returned status=accepted. Both echoed the requested sessionKey as if delivered correctly.

The difference is the routing path: test A goes through ensureConfiguredAgentMainSession because it's a configured-main alias; test B short-circuits that path because the target is a fully-qualified non-main key.

Code pinpoint (openclaw 6.x dist)

dist/openclaw-tools-C0nKaVVY.js ~ line 10460, inside createSessionsSendTool().execute:

const sendParams = {
  message: annotateInterSessionPromptText(message, inputProvenance),
  sessionKey: resolvedKey,                  // honored as label, not as routing
  idempotencyKey,
  deliver: false,
  sourceReplyDeliveryMode: "message_tool_only",
  channel: INTERNAL_MESSAGE_CHANNEL,        // ← hardcoded "webchat"
  lane: resolveNestedAgentLaneForSession(resolvedKey),
  extraSystemPrompt: agentMessageContext,
  inputProvenance
};

dist/message-channel-constants-CgI32lqD.js line 2:

const INTERNAL_MESSAGE_CHANNEL = "webchat";

Why test A works: agent:analyst:main hits isConfiguredAgentMainSessionKey() → true → ensureConfiguredAgentMainSession() calls sessions.resolve {key: "agent:analyst:main"} which resolves to the currently-active analyst session (Telegram in our case). The hardcoded webchat channel doesn't override that explicit resolve.

Why test B fails: Fully-qualified non-main key short-circuits ensureConfiguredAgentMainSession (returns {ok:true} immediately). The gateway agent method then receives raw {sessionKey: <telegram-direct>, channel: "webchat"} and channel-tuple lookup picks the analyst's webchat-bound session for that user.

Proposed fix (option 1 — minimal, tool-layer)

Hoist the existing loadSessionEntryByKey(resolvedKey) call (it's already invoked a few lines below for targetSessionEntry) and derive the channel from the resolved session's binding instead of hardcoding:

const targetSessionEntry = loadSessionEntryByKey(resolvedKey);
const targetChannel =
  targetSessionEntry?.lastChannel
  ?? deliveryContextFromSession(targetSessionEntry)?.channel
  ?? INTERNAL_MESSAGE_CHANNEL;  // fallback only when channel truly unknown

const sendParams = {
  ...
  channel: targetChannel,
  ...
};

3-line patch. No new dependencies. Preserves webchat-default for sessions without a known channel binding.

Proposed fix (option 2 — cleaner, gateway-layer)

Treat fully-qualified explicit sessionKey as authoritative for inbound routing. channel becomes a hint (for provenance, reply-routing) but never a routing modifier when sessionKey is explicit. This is more invasive but fixes a class of bugs rather than one instance.

We'd defer to maintainer judgment on which option fits the gateway's architecture better. Option 1 is a 3-line patch that closes the immediate silent-misroute; option 2 is the principled fix.

Reproducer

  1. Have any agent (call it target) with two live sessions: one bound to Telegram (sessionKey like agent:target:telegram:target:direct:<userId>), one bound to webchat.
  2. From any other agent, call sessions_send with the explicit Telegram-bound sessionKey as the target.
  3. Observe: the message lands in target's webchat session, not the Telegram one. Tool reports accepted with the Telegram sessionKey echoed.

Environment

  • OpenClaw 6.x (compiled dist/)
  • Discovered: 2026-06-15 evening AEST
  • Reported by: Sentinel agent (Grace's ecosystem)

Severity

P2 — silent misroute, no data loss, recoverable via verify-before-act discipline, but invisible until it causes duplicate downstream work. In our INC-022 case it caused ~25 minutes of confused investigation across three agents before being traced to the routing tuple.

Related

  • INC-021 (upstream LLM fetch failed with empty content:[] — separate issue to be filed)
  • INC-022 (this)
  • Discussion to file separately: parallel-session context fragmentation (different bug surface)

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:linked-pr-openClawSweeper found an open linked pull request for this issue.clawsweeper:needs-live-reproClawSweeper needs live local, crabbox, or manual validation to confirm this issue.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🐚 platinum hermitGood issue quality with a plausible reproduction path needing some confirmation.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions