Skip to content

[Bug]: Targetless message.send fails with 'Action send requires a target' in WebChat despite docs stating source-reply sink should handle it #96840

Description

@MantisCartography

Bug type

Behavior bug (incorrect output/state without crash)

Summary

Targetless message(action="send") fails with "Action send requires a target" in WebChat sessions, despite docs stating it should be projected into the active chat via the internal source-reply sink.

Environment

  • OpenClaw: 2026.5.x (installed as global npm package, minified dist)
  • Surface: WebChat (Control UI chat tab / macOS SwiftUI app)
  • Session: main agent session, direct WebChat conversation
  • Channel config: Telegram configured as the only outbound channel; WebChat has no persisted config section (per docs)

Steps to reproduce

  1. Start a WebChat session with the main agent (Control UI chat tab).
  2. During an active run, have the agent call message(action="send", message="test") with no target, to, channelId, or targets parameter.
  3. Observe the error.

Actual behavior

The tool returns:

Action send requires a target.

The message is not delivered.

Expected behavior

Per docs/web/webchat.md:

"Harnesses that require visible replies through tools.message still use WebChat as a current-run internal source reply sink. A targetless message.send from that active WebChat run is projected into the same chat and mirrored to the session transcript; WebChat does not become a reusable outbound channel and never inherits lastChannel."

A targetless message.send during an active WebChat run should be projected into the same chat as a visible reply, without requiring an explicit target.

Root cause analysis

Traced through the minified dist bundles. The failure is a three-stage miss:

1. sourceReplyDeliveryMode is never set for WebChat

In the tool resolution module (dist/tool-resolution-*.js), the sourceReplyDeliveryMode is computed as:

const sourceReplyDeliveryMode = params.sourceReplyDeliveryMode ??
  (params.inboundEventKind === "room_event" && messageProvider !== "webchat"
    ? "message_tool_only"
    : void 0);

The condition messageProvider !== "webchat" explicitly excludes WebChat. So for WebChat sessions, sourceReplyDeliveryMode is undefined.

2. shouldUseInternalSourceReplySink returns false

In dist/internal-source-reply-*.js, the first guard clause requires:

input.sourceReplyDeliveryMode === "message_tool_only"

Since sourceReplyDeliveryMode is undefined for WebChat, this returns false immediately. The source-reply sink path is never entered.

3. normalizeMessageActionInput throws the target error

In dist/message-action-runner-*.js, the execution flow is:

// line ~1335
if (await shouldUseInternalSourceReplySink(input, params))
  return handleInternalSourceReplySendAction({...input, agentId}, params);

// line ~1340 — only reached when source-reply sink returns false
params = normalizeMessageActionInput({ action, args: params, toolContext: input.toolContext });

normalizeMessageActionInput calls actionRequiresTarget("send"), which checks MESSAGE_ACTION_TARGET_MODE["send"]"to" (not "none"), so it returns true. Since no target was provided, it throws:

throw new Error(`Action ${action} requires a target.`);

Why this is a code-level gap, not a config issue

  • WebChat has no persisted config section (confirmed in docs: "Legacy channels.webchat and gateway.webchat config is retired").
  • The sourceReplyDeliveryMode assignment is hardcoded in the tool-resolution source, not driven by configuration.
  • The shouldUseInternalSourceReplySink function already has the right logic to handle WebChat (its hasCurrentSourceReplyContext function explicitly handles provider === "webchat"), but the sourceReplyDeliveryMode guard prevents it from ever being reached.

Suggested fix

In the tool-resolution source (src/agents/tool-resolution.ts or equivalent), extend the sourceReplyDeliveryMode assignment to also activate for WebChat direct sessions:

const sourceReplyDeliveryMode = params.sourceReplyDeliveryMode ??
  (params.inboundEventKind === "room_event" && messageProvider !== "webchat"
    ? "message_tool_only"
    : messageProvider === "webchat"
      ? "message_tool_only"
      : void 0);

This makes targetless message.send calls during an active WebChat run route through handleInternalSourceReplySendAction, which projects the message into the same chat — exactly what the docs describe.

Scope consideration: This activates the source-reply sink for WebChat, allowing targetless sends during an active run only. WebChat still does not become a reusable outbound channel (no lastChannel inheritance), preserving the documented boundary.

Workaround

Until the fix lands, the only options are:

  1. Include file content inline in the agent's final reply using the MEDIA: directive (works for file delivery but not for mid-run proactive messages).
  2. Route through another configured channel (e.g., Telegram) with an explicit target.
  3. Use chat.inject from the Gateway API (requires direct WebSocket access, not available from the agent tool layer).

Why this matters

WebChat is the primary interface for Control UI and macOS/iOS apps. The inability to send targetless messages during an active run limits agent UX patterns where the agent needs to deliver intermediate results, progress updates, or file attachments proactively within the same conversation turn. The docs already describe the intended behavior — this is an implementation gap.

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:queueable-fixClawSweeper marked this issue as an existing queue_fix_pr work candidate.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.no-staleExclude from stale automation

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions