Summary
When an agent uses the message tool (action=send) to deliver its main reply in a turn, models sometimes append a short trailing acknowledgement to their final reply — patterns like:
已发
已发 #22141
已发, 不再追加
主回复已发 (#xxx)
核心回答如下
总结如下
Sent above
Sent #xxx
Sent. Replied in thread.
Done.
OK
Okay
Roger
Got it
OK, that's the fix.
These acks carry no information beyond the message-tool send that already went out, but OpenClaw currently delivers them as a second visible message in the channel — a duplicate-reply failure mode.
Repro
- Send an inbound message that requires a non-trivial reply.
- Configure the agent to deliver the main reply via
message tool (action=send).
- After the agent delivers, its final reply text contains a short meta-ack (varies by model + locale).
- The channel sees two messages: the main reply + the trailing ack.
Reproduced reliably with:
- Telegram, Discord, Signal (any provider routed through
deliverOutboundPayloads).
- GPT-4o, Claude Sonnet, DeepSeek, MiniMax-M3 (no model-specific pattern — the behaviour is model-agnostic).
- English and Chinese locales.
Root cause
The existing filterMessagingToolDuplicates only catches full-content duplicates via substring match with a 10-char minimum:
const MIN_DUPLICATE_TEXT_LENGTH = 10;
Short meta-acks (e.g. 已发 #22141, 9 chars) fall under this floor and pass through, ending up as a second visible message. The current dedupe is correct for "is this a rephrasing of something I already sent" but does not handle "is this an agent ack of its own tool call".
Proposed fix
Add a complementary filter filterMessagingToolMetaCommentary that recognises post-tool-send meta commentary by pattern (Chinese + English), with conservative bounds:
- Only fires when a message-tool send has already happened this turn (
sentTexts.length > 0). Agents without message-tool sends are unaffected.
- Length-capped at 200 chars.
- Anchored ack patterns + ≤ 20 chars trailing content.
- Media payloads always preserved.
This preserves the existing dedup logic (which is correct for content duplicates) and adds a separate axis for "agent ack of own tool call" without overlap.
PR: #96680
Impact
Anyone running an agent that uses message tool to deliver replies sees duplicate messages in the channel after each non-trivial reply. The current workaround is per-agent prompting ("respond with only NO_REPLY after message tool sends"), which is fragile and model-dependent. Native dedupe fixes it once for all agents.
Summary
When an agent uses the
messagetool (action=send) to deliver its main reply in a turn, models sometimes append a short trailing acknowledgement to their final reply — patterns like:已发已发 #22141已发, 不再追加主回复已发 (#xxx)核心回答如下总结如下Sent aboveSent #xxxSent. Replied in thread.Done.OKOkayRogerGot itOK, that's the fix.These acks carry no information beyond the message-tool send that already went out, but OpenClaw currently delivers them as a second visible message in the channel — a duplicate-reply failure mode.
Repro
messagetool (action=send).Reproduced reliably with:
deliverOutboundPayloads).Root cause
The existing
filterMessagingToolDuplicatesonly catches full-content duplicates via substring match with a 10-char minimum:Short meta-acks (e.g.
已发 #22141, 9 chars) fall under this floor and pass through, ending up as a second visible message. The current dedupe is correct for "is this a rephrasing of something I already sent" but does not handle "is this an agent ack of its own tool call".Proposed fix
Add a complementary filter
filterMessagingToolMetaCommentarythat recognises post-tool-send meta commentary by pattern (Chinese + English), with conservative bounds:sentTexts.length > 0). Agents without message-tool sends are unaffected.This preserves the existing dedup logic (which is correct for content duplicates) and adds a separate axis for "agent ack of own tool call" without overlap.
PR: #96680
Impact
Anyone running an agent that uses
messagetool to deliver replies sees duplicate messages in the channel after each non-trivial reply. The current workaround is per-agent prompting ("respond with only NO_REPLY after message tool sends"), which is fragile and model-dependent. Native dedupe fixes it once for all agents.