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:
- First as a single streaming card (correct, complete content)
- 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
- Configure Feishu channel with
streaming: true, renderMode: "card"
- Send a message that triggers a long response with tool calls (e.g. querying a project management API that returns a large structured summary)
- 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.
Description
When
streaming: trueandrenderMode: "card"are both enabled for a Feishu channel, long responses (e.g. multi-table project summaries ~4000+ chars) are delivered twice:textChunkLimit)Root cause analysis
In
extensions/feishu/src/reply-dispatcher.ts, thedeliveredFinalTextsdedup (line 250) compares the final callbacktextagainst previously delivered texts using exact string matching (Set.has()).For long responses,
streamText(built viamergeStreamingText()from partial updates) can diverge from thetextin thedeliver()final callback — e.g. due to partial overlap merging, whitespace normalization, or delta vs snapshot mode differences. WhencloseStreaming()addstexttodeliveredFinalTexts(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
streaming: true,renderMode: "card"Expected behavior
The response should be delivered only once — either as a streaming card OR as chunked cards, not both.
Environment
streaming: true,renderMode: "card"Workaround
Set
streaming: falsein the Feishu channel config to disable streaming cards entirely.