openclaw - ✅(Solved) Fix [Bug]: `message` tool rejects model-generated `SendMessage` instead of normalizing it to `message` [1 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#84079Fetched 2026-05-20 03:44:17
View on GitHub
Comments
1
Participants
2
Timeline
7
Reactions
1
Author
Timeline (top)
referenced ×3labeled ×2commented ×1cross-referenced ×1

OpenClaw 2026.5.18 can lose visible Discord status/final replies when an Anthropic/Pi agent invokes the core message tool with a non-canonical text argument named SendMessage.

The model chooses the correct tool (message) and generally correct routing (target: "channel:<id>"), but emits the send body as SendMessage instead of the expected message. The dispatcher then rejects the call with message required before Discord delivery.

This is separate from the Codex app-server stall issue. In this case, the agent can complete useful work and fail only when trying to report progress or completion back to Discord.

Error Message

"status": "error", "error": "message required"

Root Cause

  • User sees no progress or final result in Discord even when the agent worked successfully.
  • The agent appears stalled or silent.
  • Status and completion reporting are unreliable for Anthropic/Pi-backed Discord sessions.
  • The issue compounds other runtime failures because even recovery/status messages may fail to deliver.

Fix Action

Fix / Workaround

The model chooses the correct tool (message) and generally correct routing (target: "channel:<id>"), but emits the send body as SendMessage instead of the expected message. The dispatcher then rejects the call with message required before Discord delivery.

The dispatcher rejects the call before delivery:

Workaround for critical status/final delivery:

PR fix notes

PR #84102: fix #84079: normalize SendMessage/content/text aliases to message before send validation

Description (problem / solution / changelog)

Summary

Fixes #84079

Issue

message tool rejects model-generated SendMessage instead of normalizing it to message

Changes

  • Normalize common text-body aliases (SendMessage, content, text) to the canonical message field before send validation
  • Add diagnostic warning log when alias normalization is applied
  • Add comprehensive tests for alias normalization

Changed Files

 CHANGELOG.md                                       |  1 +
 src/infra/outbound/message-action-runner.ts        | 11 +++
 src/infra/outbound/message-action-runner.send-validation.test.ts | 85 +++++++++++++++++++++-
 3 files changed, 96 insertions(+), 1 deletion(-)

Real behavior proof

Behavior addressed: PR #84102 fixes message send body alias handling so SendMessage, content, and text are normalized to message before the outbound send path validates and dispatches a send action. It also fixes the ClawSweeper follow-up by sanitizing SendMessage alias content before delivery, so hidden reasoning text is not sent.

Real environment tested: Linux LIN-404B39D3E3F.zte.intra 4.19.112-2.el8.x86_64, Node v22.22.0, pnpm 11.1.0, worktree /media/vdc/code/ai/openclaw-84079, PR head 583e9827bcc385ea51a766a0a0644099ece4c986. OpenClaw was started from this worktree with isolated OPENCLAW_HOME=/media/vdc/code/ai/openclaw-84079/.automation/openclaw-home, isolated OPENCLAW_STATE_DIR=/media/vdc/code/ai/openclaw-84079/.automation/openclaw-home/state, isolated OPENCLAW_CONFIG_PATH=/media/vdc/code/ai/openclaw-84079/.automation/openclaw-home/state/openclaw.json, and OPENCLAW_GATEWAY_PORT=19102.

Exact steps or command run after the patch:

OPENCLAW_HOME=/media/vdc/code/ai/openclaw-84079/.automation/openclaw-home \
OPENCLAW_STATE_DIR=/media/vdc/code/ai/openclaw-84079/.automation/openclaw-home/state \
OPENCLAW_CONFIG_PATH=/media/vdc/code/ai/openclaw-84079/.automation/openclaw-home/state/openclaw.json \
OPENCLAW_GATEWAY_PORT=19102 \
OPENCLAW_SKIP_CHANNELS=1 \
pnpm openclaw gateway --port 19102 --verbose --allow-unconfigured
OPENCLAW_HOME=/media/vdc/code/ai/openclaw-84079/.automation/openclaw-home \
OPENCLAW_STATE_DIR=/media/vdc/code/ai/openclaw-84079/.automation/openclaw-home/state \
OPENCLAW_CONFIG_PATH=/media/vdc/code/ai/openclaw-84079/.automation/openclaw-home/state/openclaw.json \
OPENCLAW_GATEWAY_PORT=19102 \
OPENCLAW_SKIP_CHANNELS=1 \
node --import tsx .automation/proof-message-alias.ts
pnpm test src/infra/outbound/message-action-runner.send-validation.test.ts -- --reporter=verbose
pnpm check:changed

Evidence after fix: Terminal output from the real OpenClaw setup showed the gateway reached ready state and listened on the isolated proof port:

2026-05-20T00:21:27.331+08:00 [gateway] loading configuration...
2026-05-20T00:21:27.611+08:00 [gateway] starting...
2026-05-20T00:21:34.027+08:00 [gateway] starting HTTP server...
2026-05-20T00:21:38.706+08:00 [gateway] http server listening (8 plugins: acpx, browser, canvas, device-pair, file-transfer, memory-core, phone-control, talk-voice; 11.1s)
2026-05-20T00:21:38.718+08:00 [gateway/channels] skipping channel start (OPENCLAW_SKIP_CHANNELS=1 or OPENCLAW_SKIP_PROVIDERS=1)
2026-05-20T00:21:39.652+08:00 [gateway] ready
$ ss -ltnp | grep 19102
LISTEN 0      128             127.0.0.1:19102      0.0.0.0:*    users:(("openclaw",pid=1469069,fd=22))
LISTEN 0      128                 [::1]:19102         [::]:*    users:(("openclaw",pid=1469069,fd=23))

The proof script registered a temporary proofsend channel plugin with a real direct outbound sendText adapter and ran runMessageAction({ action: "send", dryRun: false }). Copied live output:

[message-tool] normalized alias "SendMessage" to "message" for send action
{"alias":"SendMessage","result":{"kind":"send","channel":"proofsend","to":"C84102","handledBy":"core","dryRun":false,"sendResult":{"channel":"proofsend","to":"C84102","via":"direct","mediaUrl":null,"result":{"channel":"proofsend","messageId":"proof-1"}}},"delivered":{"to":"C84102","text":"alias via SendMessage"}}
[message-tool] normalized alias "content" to "message" for send action
{"alias":"content","result":{"kind":"send","channel":"proofsend","to":"C84102","handledBy":"core","dryRun":false,"sendResult":{"channel":"proofsend","to":"C84102","via":"direct","mediaUrl":null,"result":{"channel":"proofsend","messageId":"proof-2"}}},"delivered":{"to":"C84102","text":"alias via content"}}
[message-tool] normalized alias "text" to "message" for send action
{"alias":"text","result":{"kind":"send","channel":"proofsend","to":"C84102","handledBy":"core","dryRun":false,"sendResult":{"channel":"proofsend","to":"C84102","via":"direct","mediaUrl":null,"result":{"channel":"proofsend","messageId":"proof-3"}}},"delivered":{"to":"C84102","text":"alias via text"}}
[message-tool] normalized alias "SendMessage" to "message" for send action
{"sanitizerCase":"reasoning-tag","result":{"kind":"send","channel":"proofsend","to":"C84102","handledBy":"core","dryRun":false,"sendResult":{"channel":"proofsend","to":"C84102","via":"direct","mediaUrl":null,"result":{"channel":"proofsend","messageId":"proof-4"}}},"delivered":{"to":"C84102","text":"Visible answer"}}
[message-tool] normalized alias "SendMessage" to "message" for send action
{"sanitizerCase":"formatted-reasoning","result":{"kind":"send","channel":"proofsend","to":"C84102","handledBy":"core","dryRun":false,"sendResult":{"channel":"proofsend","to":"C84102","via":"direct","mediaUrl":null,"result":{"channel":"proofsend","messageId":"proof-5"}}},"delivered":{"to":"C84102","text":"Visible answer"}}
{"missingMessageRejected":true,"error":"message required"}

Targeted test output:

$ pnpm test src/infra/outbound/message-action-runner.send-validation.test.ts -- --reporter=verbose
Test Files  1 passed (1)
Tests  18 passed (18)
[test] passed 1 Vitest shard in 11.63s

Changed check output:

$ pnpm check:changed
[check:changed] lanes=core, coreTests, docs, tooling
Found 0 warnings and 0 errors.
Import cycle check: 0 runtime value cycle(s).
Exit code: 0

Observed result after fix: The isolated OpenClaw gateway reached [gateway] ready on port 19102. SendMessage, content, and text each reached the actual outbound direct send path with dryRun:false and returned message ids proof-1, proof-2, and proof-3. SendMessage containing <think>internal reasoning</think>Visible answer delivered only Visible answer; SendMessage containing Reasoning:\n_internal plan_\n\nVisible answer also delivered only Visible answer. A send with no message and no alias still rejects with message required.

What was not tested: No live external provider account was used. The proof intentionally uses an isolated test channel adapter to avoid sending real messages to Slack, Feishu, or other services. Channels were skipped during startup with OPENCLAW_SKIP_CHANNELS=1 to keep this PR proof isolated from local personal channel credentials and currently running Co-Claw/OpenClaw instances. Browser UI interaction was not required for this backend send-path fix.

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • src/infra/outbound/message-action-runner.send-validation.test.ts (modified, +117/-1)
  • src/infra/outbound/message-action-runner.ts (modified, +46/-0)

Code Example

{
  "role": "assistant",
  "content": [
    {
      "type": "toolCall",
      "name": "message",
      "arguments": {
        "action": "send",
        "target": "channel:1497109509825626232",
        "SendMessage": "Subagent läuft. Er schreibt die Website komplett als 5-seitige WordPress-Seitenstruktur um..."
      }
    }
  ],
  "provider": "anthropic",
  "model": "claude-sonnet-4-6",
  "stopReason": "toolUse"
}

---

{
  "role": "assistant",
  "content": [
    {
      "type": "toolCall",
      "name": "message",
      "arguments": {
        "action": "send",
        "target": "channel:1497109509825626232",
        "SendMessage": "Subagent läuft. Er baut eine echte statische Multi-Page-Site unter `artifacts/test/site/`..."
      }
    }
  ],
  "provider": "anthropic",
  "model": "claude-opus-4-7",
  "stopReason": "toolUse"
}

---

{
  "action": "send",
  "target": "channel:1497109509825626232",
  "SendMessage": "Status text"
}

---

{
  "action": "send",
  "target": "channel:1497109509825626232",
  "message": "Status text"
}

---

{
  "status": "error",
  "tool": "message",
  "error": "message required"
}

---

{
  "models": {
    "providers": {
      "anthropic": {
        "baseUrl": "http://127.0.0.1:18801",
        "api": "anthropic-messages",
        "models": [
          { "id": "claude-sonnet-4-6" },
          { "id": "claude-opus-4-6" },
          { "id": "claude-opus-4-7" }
        ]
      }
    }
  }
}

---

channel:1497109509825626232

---

openclaw message send   --channel discord   --target channel:1497109509825626232   --message "..."

---

{
  "name": "message",
  "arguments": {
    "action": "send",
    "target": "channel:1497109509825626232",
    "message": "..."
  }
}

---

Gateway log examples:


2026-05-19T07:24:53.591+00:00 [tools]
message failed: message required
raw_params={
  "action":"send",
  "target":"channel:1497109509825626232",
  "SendMessage":"Subagent läuft..."
}



2026-05-19T08:37:00.283+00:00 [tools]
message failed: message required
raw_params={
  "action":"send",
  "target":"channel:1497109509825626232",
  "SendMessage":"Subagent läuft..."
}



2026-05-19T08:42:33.914+00:00 [tools]
message failed: message required
raw_params={
  "action":"send",
  "target":"channel:1497109509825626232",
  "SendMessage":"Du hattest recht — der Subagent ist nie sauber durchgekommen..."
}



2026-05-19T08:42:42.988+00:00 [tools]
message failed: message required
raw_params={
  "action":"send",
  "target":"channel:1497109509825626232",
  "SendMessage":"Du hattest recht: Subagent ist nie sauber durchgekommen..."
}

---

function normalizeMessageSendArgs(params) {
  if (typeof params.message !== "string" || !params.message.trim()) {
    if (typeof params.SendMessage === "string") params.message = params.SendMessage;
    else if (typeof params.content === "string") params.message = params.content;
    else if (typeof params.text === "string") params.message = params.text;
  }
  return params;
}

---

[tools] normalized message send alias SendMessage -> message provider=anthropic model=claude-opus-4-7

---

openclaw message send --channel discord --target channel:<id> --message "..."
RAW_BUFFERClick to expand / collapse

Bug type

Regression (worked before, now fails)

Beta release blocker

No

Summary

OpenClaw 2026.5.18 can lose visible Discord status/final replies when an Anthropic/Pi agent invokes the core message tool with a non-canonical text argument named SendMessage.

The model chooses the correct tool (message) and generally correct routing (target: "channel:<id>"), but emits the send body as SendMessage instead of the expected message. The dispatcher then rejects the call with message required before Discord delivery.

This is separate from the Codex app-server stall issue. In this case, the agent can complete useful work and fail only when trying to report progress or completion back to Discord.

Steps to reproduce

  1. Run OpenClaw with a Discord channel connected.
  2. Use an Anthropic/Pi-backed agent, for example anthropic/claude-sonnet-4-6 or anthropic/claude-opus-4-7.
  3. Ask the agent to perform a multi-step task in Discord where it should send progress or final status via the message tool.
  4. Inspect the session JSONL and gateway logs when no Discord status/result arrives.

Observed tool call shape in JSONL:

{
  "role": "assistant",
  "content": [
    {
      "type": "toolCall",
      "name": "message",
      "arguments": {
        "action": "send",
        "target": "channel:1497109509825626232",
        "SendMessage": "Subagent läuft. Er schreibt die Website komplett als 5-seitige WordPress-Seitenstruktur um..."
      }
    }
  ],
  "provider": "anthropic",
  "model": "claude-sonnet-4-6",
  "stopReason": "toolUse"
}

The same shape was later reproduced with claude-opus-4-7:

{
  "role": "assistant",
  "content": [
    {
      "type": "toolCall",
      "name": "message",
      "arguments": {
        "action": "send",
        "target": "channel:1497109509825626232",
        "SendMessage": "Subagent läuft. Er baut eine echte statische Multi-Page-Site unter `artifacts/test/site/`..."
      }
    }
  ],
  "provider": "anthropic",
  "model": "claude-opus-4-7",
  "stopReason": "toolUse"
}

Expected behavior

OpenClaw should either:

  1. present a tool schema/prompt that reliably causes Anthropic/Pi models to use the canonical message field, or
  2. normalize common text-body aliases before validation.

At minimum, this model-generated call:

{
  "action": "send",
  "target": "channel:1497109509825626232",
  "SendMessage": "Status text"
}

should be canonicalized to:

{
  "action": "send",
  "target": "channel:1497109509825626232",
  "message": "Status text"
}

The Discord channel should then receive the status/final answer.

Actual behavior

The dispatcher rejects the call before delivery:

{
  "status": "error",
  "tool": "message",
  "error": "message required"
}

The Discord channel receives no update. The agent may retry the same broken call shape multiple times. In some runs, the final answer is written only to the transcript and not delivered to Discord.

OpenClaw version

OpenClaw 2026.5.18 (50a2481)

Operating system

Ubuntu

Install method

npm global

Model

claude-opus-4-7

Provider / routing chain

anthropic/claude-opus-4-7 -> OpenClaw Pi/default embedded runner -> core message tool -> Discord channel

Additional provider/model setup details

Anthropic is configured through a local billing proxy:

{
  "models": {
    "providers": {
      "anthropic": {
        "baseUrl": "http://127.0.0.1:18801",
        "api": "anthropic-messages",
        "models": [
          { "id": "claude-sonnet-4-6" },
          { "id": "claude-opus-4-6" },
          { "id": "claude-opus-4-7" }
        ]
      }
    }
  }
}

The Discord target is a channel target and works when addressed as:

channel:1497109509825626232

Control case using the CLI succeeded:

openclaw message send   --channel discord   --target channel:1497109509825626232   --message "..."

That delivered to Discord with message id 1506218532638298142.

Codex-generated message tool calls in the same environment used the canonical field and delivered successfully when the tool call completed:

{
  "name": "message",
  "arguments": {
    "action": "send",
    "target": "channel:1497109509825626232",
    "message": "..."
  }
}

Logs, screenshots, and evidence

Gateway log examples:


2026-05-19T07:24:53.591+00:00 [tools]
message failed: message required
raw_params={
  "action":"send",
  "target":"channel:1497109509825626232",
  "SendMessage":"Subagent läuft..."
}



2026-05-19T08:37:00.283+00:00 [tools]
message failed: message required
raw_params={
  "action":"send",
  "target":"channel:1497109509825626232",
  "SendMessage":"Subagent läuft..."
}



2026-05-19T08:42:33.914+00:00 [tools]
message failed: message required
raw_params={
  "action":"send",
  "target":"channel:1497109509825626232",
  "SendMessage":"Du hattest recht — der Subagent ist nie sauber durchgekommen..."
}



2026-05-19T08:42:42.988+00:00 [tools]
message failed: message required
raw_params={
  "action":"send",
  "target":"channel:1497109509825626232",
  "SendMessage":"Du hattest recht: Subagent ist nie sauber durchgekommen..."
}

Impact and severity

Severity: high for user-facing Discord workflows.

Impact:

  • User sees no progress or final result in Discord even when the agent worked successfully.
  • The agent appears stalled or silent.
  • Status and completion reporting are unreliable for Anthropic/Pi-backed Discord sessions.
  • The issue compounds other runtime failures because even recovery/status messages may fail to deliver.

Additional information

Suggested fix direction: add model-agnostic alias normalization before the message tool send payload is validated.

Pseudo-code:

function normalizeMessageSendArgs(params) {
  if (typeof params.message !== "string" || !params.message.trim()) {
    if (typeof params.SendMessage === "string") params.message = params.SendMessage;
    else if (typeof params.content === "string") params.message = params.content;
    else if (typeof params.text === "string") params.message = params.text;
  }
  return params;
}

This should happen centrally in the core message action path, not only in a model-specific prompt. Different providers/runtimes may generate slightly different argument names.

A diagnostic warning when alias normalization is applied would also help:

[tools] normalized message send alias SendMessage -> message provider=anthropic model=claude-opus-4-7

Workaround for critical status/final delivery:

openclaw message send --channel discord --target channel:<id> --message "..."

Prompting agents with “When using message(action="send"), put the body in message, never SendMessage” may reduce occurrences, but is not robust enough as the only workaround.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

OpenClaw should either:

  1. present a tool schema/prompt that reliably causes Anthropic/Pi models to use the canonical message field, or
  2. normalize common text-body aliases before validation.

At minimum, this model-generated call:

{
  "action": "send",
  "target": "channel:1497109509825626232",
  "SendMessage": "Status text"
}

should be canonicalized to:

{
  "action": "send",
  "target": "channel:1497109509825626232",
  "message": "Status text"
}

The Discord channel should then receive the status/final answer.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix [Bug]: `message` tool rejects model-generated `SendMessage` instead of normalizing it to `message` [1 pull requests, 1 comments, 2 participants]