openclaw - 💡(How to fix) Fix Non-streaming dispatchReplyFromConfig delivers final reply payload twice

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…

When a channel plugin uses dispatchReplyFromConfig with disableBlockStreaming: true, the final reply payload is delivered twice to the plugin's deliver callback. Each delivery has the same text, mediaUrl, and contextToken, spaced ~1.1 seconds apart (the second triggers right after the first's async send completes).

Root Cause

Looking at dispatch-BlRYQnj0.js:

The dispatchReplyFromConfig function processes replies from getReplyFromConfig:

const replies = replyResult ? (Array.isArray(replyResult) ? replyResult : [replyResult]) : [];
for (const reply of replies) {
    // ...
    const finalReply = await sendFinalPayload(reply);
}

The replyResult from getReplyFromConfig appears to contain two entries for a single agent response when disableBlockStreaming: true is set. The exact cause is in how the non-streaming result is split into multiple payloads.

Each sendFinalPayload call produces a delivery, and both result in the same media being uploaded and sent to the user.

Fix Action

Fix / Workaround

Non-streaming reply dispatcher delivers final payload twice

When a channel plugin uses dispatchReplyFromConfig with disableBlockStreaming: true, the final reply payload is delivered twice to the plugin's deliver callback. Each delivery has the same text, mediaUrl, and contextToken, spaced ~1.1 seconds apart (the second triggers right after the first's async send completes).

  • Any channel using MEDIA: directives for image delivery gets two identical images sent to the user
  • Text-only replies also get sent twice (confirmed by plugin logs)
  • Affects all non-streaming replies routed via dispatchReplyFromConfig

Code Example

23:39:10.082 [outbound] textLen=0 mediaUrl=present
23:39:10.082 [sendWeixinMediaFile] uploading image screen---be549f50
23:39:10.996 [upload done] filekey=b06fbd440410818c
23:39:11.233 [outbound: media sent OK]
23:39:11.233 [outbound] textLen=0 mediaUrl=present      ← SECOND DELIVERY, same data
23:39:11.234 [sendWeixinMediaFile] uploading image screen---be549f50  ← uploads AGAIN
23:39:11.817 [upload done] filekey=67c2bbfd48fcf82f
23:39:12.026 [outbound: media sent OK]

---

const replies = replyResult ? (Array.isArray(replyResult) ? replyResult : [replyResult]) : [];
for (const reply of replies) {
    // ...
    const finalReply = await sendFinalPayload(reply);
}

---

const deliverDedupCache = new Map();
const DELIVER_DEDUP_WINDOW_MS = 3000;
function isDuplicatedDelivery(mediaUrl) {
    const now = Date.now();
    const last = deliverDedupCache.get(mediaUrl);
    if (last && now - last < DELIVER_DEDUP_WINDOW_MS) return true;
    deliverDedupCache.set(mediaUrl, now);
    return false;
}
RAW_BUFFERClick to expand / collapse

Non-streaming reply dispatcher delivers final payload twice

Description

When a channel plugin uses dispatchReplyFromConfig with disableBlockStreaming: true, the final reply payload is delivered twice to the plugin's deliver callback. Each delivery has the same text, mediaUrl, and contextToken, spaced ~1.1 seconds apart (the second triggers right after the first's async send completes).

Impact

  • Any channel using MEDIA: directives for image delivery gets two identical images sent to the user
  • Text-only replies also get sent twice (confirmed by plugin logs)
  • Affects all non-streaming replies routed via dispatchReplyFromConfig

Reproduction

  1. Configure a channel plugin that uses createReplyDispatcherWithTyping + dispatchReplyFromConfig with disableBlockStreaming: true
  2. Have the agent respond with a MEDIA:<path> directive (or any text)
  3. Observe deliver() callback called twice with identical payload

Log evidence

From WeChat plugin gateway log (openclaw-weixin), every reply produces two sets of delivery:

23:39:10.082 [outbound] textLen=0 mediaUrl=present
23:39:10.082 [sendWeixinMediaFile] uploading image screen---be549f50
23:39:10.996 [upload done] filekey=b06fbd440410818c
23:39:11.233 [outbound: media sent OK]
23:39:11.233 [outbound] textLen=0 mediaUrl=present      ← SECOND DELIVERY, same data
23:39:11.234 [sendWeixinMediaFile] uploading image screen---be549f50  ← uploads AGAIN
23:39:11.817 [upload done] filekey=67c2bbfd48fcf82f
23:39:12.026 [outbound: media sent OK]

Root cause analysis

Looking at dispatch-BlRYQnj0.js:

The dispatchReplyFromConfig function processes replies from getReplyFromConfig:

const replies = replyResult ? (Array.isArray(replyResult) ? replyResult : [replyResult]) : [];
for (const reply of replies) {
    // ...
    const finalReply = await sendFinalPayload(reply);
}

The replyResult from getReplyFromConfig appears to contain two entries for a single agent response when disableBlockStreaming: true is set. The exact cause is in how the non-streaming result is split into multiple payloads.

Each sendFinalPayload call produces a delivery, and both result in the same media being uploaded and sent to the user.

Temporary workaround (plugin side)

Added a dedup cache in the WeChat plugin's deliver function that skips duplicate media deliveries within a 3-second window:

const deliverDedupCache = new Map();
const DELIVER_DEDUP_WINDOW_MS = 3000;
function isDuplicatedDelivery(mediaUrl) {
    const now = Date.now();
    const last = deliverDedupCache.get(mediaUrl);
    if (last && now - last < DELIVER_DEDUP_WINDOW_MS) return true;
    deliverDedupCache.set(mediaUrl, now);
    return false;
}

Version

  • OpenClaw 2026.5.18 (50a2481)
  • Plugin: openclaw-weixin (in-channel plugin via createReplyDispatcherWithTyping)

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…

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 - 💡(How to fix) Fix Non-streaming dispatchReplyFromConfig delivers final reply payload twice