openclaw - 💡(How to fix) Fix Feishu: duplicate card delivery for long streaming responses [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#43612Fetched 2026-04-08 00:16:49
View on GitHub
Comments
1
Participants
2
Timeline
1
Reactions
0
Author
Timeline (top)
commented ×1

When streaming: true and renderMode: "card" are both enabled for a Feishu channel, long responses (e.g. multi-table project summaries ~4000+ chars) are delivered twice:

  1. First as a single streaming card (correct, complete content)
  2. Then again as multiple chunked cards (same content, split by textChunkLimit)

Root Cause

In extensions/feishu/src/reply-dispatcher.ts, the deliveredFinalTexts dedup (line 250) compares the final callback text against previously delivered texts using exact string matching (Set.has()).

For long responses, streamText (built via mergeStreamingText() from partial updates) can diverge from the text in the deliver() final callback — e.g. due to partial overlap merging, whitespace normalization, or delta vs snapshot mode differences. When closeStreaming() adds text to deliveredFinalTexts (line 288), but a subsequent final delivery arrives with slightly different text, the dedup check fails and the text is re-sent via the non-streaming chunked path (lines 306-323).

Fix Action

Workaround

Set streaming: false in the Feishu channel config to disable streaming cards entirely.

RAW_BUFFERClick to expand / collapse

Description

When streaming: true and renderMode: "card" are both enabled for a Feishu channel, long responses (e.g. multi-table project summaries ~4000+ chars) are delivered twice:

  1. First as a single streaming card (correct, complete content)
  2. Then again as multiple chunked cards (same content, split by textChunkLimit)

Root cause analysis

In extensions/feishu/src/reply-dispatcher.ts, the deliveredFinalTexts dedup (line 250) compares the final callback text against previously delivered texts using exact string matching (Set.has()).

For long responses, streamText (built via mergeStreamingText() from partial updates) can diverge from the text in the deliver() final callback — e.g. due to partial overlap merging, whitespace normalization, or delta vs snapshot mode differences. When closeStreaming() adds text to deliveredFinalTexts (line 288), but a subsequent final delivery arrives with slightly different text, the dedup check fails and the text is re-sent via the non-streaming chunked path (lines 306-323).

Steps to reproduce

  1. Configure Feishu channel with streaming: true, renderMode: "card"
  2. Send a message that triggers a long response with tool calls (e.g. querying a project management API that returns a large structured summary)
  3. Observe: streaming card shows complete response, then same content appears again as multiple smaller cards

Expected behavior

The response should be delivered only once — either as a streaming card OR as chunked cards, not both.

Environment

  • OpenClaw version: 2026.3.7
  • Channel: Feishu (websocket mode)
  • Config: streaming: true, renderMode: "card"

Workaround

Set streaming: false in the Feishu channel config to disable streaming cards entirely.

extent analysis

Fix Plan

To fix the issue of duplicate message delivery, we need to modify the deduplication logic in extensions/feishu/src/reply-dispatcher.ts.

  • Update the deliveredFinalTexts Set to store a normalized version of the text.
  • Normalize the text in the deliver() final callback before comparing it with the stored texts.

Code Changes

// extensions/feishu/src/reply-dispatcher.ts

// ...

// Normalize text by removing whitespace and converting to lowercase
function normalizeText(text: string): string {
  return text.trim().toLowerCase();
}

// ...

// Update deliveredFinalTexts to store normalized text
const deliveredFinalTexts: Set<string> = new Set();

// ...

// In closeStreaming()
deliveredFinalTexts.add(normalizeText(text));

// ...

// In deliver()
if (deliveredFinalTexts.has(normalizeText(text))) {
  // Text has already been delivered, skip
  return;
}

// ...

Verification

To verify the fix, follow these steps:

  • Configure the Feishu channel with streaming: true and renderMode: "card".
  • Send a message that triggers a long response.
  • Observe that the response is delivered only once, either as a streaming card or as chunked cards.

Extra Tips

  • Make sure to test the fix with different types of messages and responses to ensure it works correctly in all scenarios.
  • Consider adding additional logging to track when messages are skipped due to deduplication.

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

The response should be delivered only once — either as a streaming card OR as chunked cards, not both.

Still need to ship something?

×6

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

Back to top recommendations

TRENDING