openclaw - 💡(How to fix) Fix Feishu streaming card multiple content delivery bugs cause final text loss stale content and duplication [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#77685Fetched 2026-05-06 06:23:04
View on GitHub
Comments
1
Participants
2
Timeline
1
Reactions
2
Author
Timeline (top)
commented ×1

The Feishu streaming card (monitor.account-BJ8FoDGL.js in @openclaw/feishu) has multiple interconnected bugs that cause final reply text to be lost, stale content to persist, or duplicate content in the card. These issues are most visible when:

  1. Tool calls fail and the error message blocks the final response
  2. REACT agent emits multiple independent text blocks
  3. Long-running operations cause the streaming card to idle-timeout
  4. Stream throttling leaves stale pending content that overwrites the final text

Error Message

  1. Tool calls fail and the error message blocks the final response Root cause: FeishuStreamingSession.update uses this.pendingText ?? this.state.currentText as the merge input. When a short block update (e.g., tool error message) gets throttled via schedulePendingFlush, this.pendingText retains the stale content. The next streaming.update call reads this stale pendingText instead of the reset currentText: Bug 6 (pendingText stale): Use a REACT agent that calls a tool and the tool fails. The error block update (short text) gets throttled, pendingText preserves the error. Final text either concatenates with error or is completely lost.

Root Cause

Root cause: When deliver(final) calls flushStreamingCardUpdate, it chains on partialUpdateQueue but returns immediately. The close() method on the streaming session then calls clearFlushTimer() which cancels pending updates, causing duplicate text re-delivery.

Fix Action

Fix / Workaround

Root cause: FeishuStreamingSession.update uses this.pendingText ?? this.state.currentText as the merge input. When a short block update (e.g., tool error message) gets throttled via schedulePendingFlush, this.pendingText retains the stale content. The next streaming.update call reads this stale pendingText instead of the reset currentText:

Code Example

flushStreamingCardUpdate(buildCombinedStreamText(reasoningText, streamText));
if (streaming?.isActive()) await partialUpdateQueue;

---

// Before:
this.closed = true;
this.clearFlushTimer();
await this.queue;

// After:
this.closed = true;
await this.queue;
clearTimeout(this.flushTimer);
this.flushTimer = null;

---

// Before:
queueStreamingUpdate(text, {
    mode: "delta",
    dedupeWithLastPartial: true
});

// After:
queueStreamingUpdate(text, {
    dedupeWithLastPartial: true
});

---

// In flushStreamingCardUpdate:
if (streaming.state) streaming.state.currentText = "";
await streaming.update(combined);

---

// Track last block text:
if (info?.kind === "block") {
    lastBlockText = text;
    queueStreamingUpdate(text, { dedupeWithLastPartial: true });
}

// Extract suffix in final handler:
if (info?.kind === "final") {
    const suffixStart = lastBlockText ? text.lastIndexOf(lastBlockText) : -1;
    if (suffixStart >= 0) {
        const suffix = text.slice(suffixStart + lastBlockText.length);
        streamText = lastBlockText + suffix;
    } else {
        streamText = text;
    }
    // ... flush
}

---

const skipTextForClosedStreamingFinal = info?.kind === "final" && hasText && 
    streamingClosedForReply && !streamingCloseErroredForReply && 
    streamingEnabled && useCard;

---

const skipTextForClosedStreamingFinal = info?.kind === "final" && hasText && 
    streamingClosedForReply && !streamingCloseErroredForReply && 
    streamingEnabled && useCard && deliveredFinalTexts.has(text);

---

async update(text) {
    // ...
    const mergedInput = mergeStreamingText(
        this.pendingText ?? this.state.currentText,  // BUG: stale pendingText!
        text
    );

---

// In flushStreamingCardUpdate:
if (streaming.state) streaming.state.currentText = "";
streaming.pendingText = null;
if (streaming.flushTimer) { clearTimeout(streaming.flushTimer); streaming.flushTimer = null; }
await streaming.update(combined);
RAW_BUFFERClick to expand / collapse

Bug: Feishu streaming card content delivery issues - final text lost or showing stale content

Description

The Feishu streaming card (monitor.account-BJ8FoDGL.js in @openclaw/feishu) has multiple interconnected bugs that cause final reply text to be lost, stale content to persist, or duplicate content in the card. These issues are most visible when:

  1. Tool calls fail and the error message blocks the final response
  2. REACT agent emits multiple independent text blocks
  3. Long-running operations cause the streaming card to idle-timeout
  4. Stream throttling leaves stale pending content that overwrites the final text

Bug 1: deliver(final) doesn't wait for streaming update queue (Fix 1+2)

Root cause: When deliver(final) calls flushStreamingCardUpdate, it chains on partialUpdateQueue but returns immediately. The close() method on the streaming session then calls clearFlushTimer() which cancels pending updates, causing duplicate text re-delivery.

Fix 1 (~line 1469): Add await partialUpdateQueue after flushing final text:

flushStreamingCardUpdate(buildCombinedStreamText(reasoningText, streamText));
if (streaming?.isActive()) await partialUpdateQueue;

Fix 2 (in close() method, ~line 945): Replace clearFlushTimer() with explicit clearTimeout:

// Before:
this.closed = true;
this.clearFlushTimer();
await this.queue;

// After:
this.closed = true;
await this.queue;
clearTimeout(this.flushTimer);
this.flushTimer = null;

Bug 2: REACT agent block updates use "delta" mode causing content accumulation (Fix 3)

Root cause: When REACT agent outputs tool call status as blocks, each block is sent with mode: "delta" (append), causing the streamText variable to accumulate ALL previous blocks. When mergeStreamingText cannot find text overlap, it concatenates old and new content.

Fix 3 (~line 1310): Change block updates to default mode: "snapshot" (replace):

// Before:
queueStreamingUpdate(text, {
    mode: "delta",
    dedupeWithLastPartial: true
});

// After:
queueStreamingUpdate(text, {
    dedupeWithLastPartial: true
});

Bug 3: streaming.state.currentText not reset before final text merge (Fix 4)

Root cause: flushStreamingCardUpdate calls streaming.update(combined) which internally calls mergeStreamingText(this.state.currentText, combined). If currentText still holds the old block content, mergeStreamingText concatenates old and new text.

Fix 4 (~line 1276): Reset currentText before each streaming update:

// In flushStreamingCardUpdate:
if (streaming.state) streaming.state.currentText = "";
await streaming.update(combined);

Bug 4: Final text suffix extraction loses content context (Fix 5)

Root cause: When REACT agent delivers the final text, it contains ALL previous block content as context. Without extracting the last block text as a suffix anchor, streamText may lose content or produce duplicates.

Fix 5 (~line 1393, 1464): Track lastBlockText and extract only unique suffix:

// Track last block text:
if (info?.kind === "block") {
    lastBlockText = text;
    queueStreamingUpdate(text, { dedupeWithLastPartial: true });
}

// Extract suffix in final handler:
if (info?.kind === "final") {
    const suffixStart = lastBlockText ? text.lastIndexOf(lastBlockText) : -1;
    if (suffixStart >= 0) {
        const suffix = text.slice(suffixStart + lastBlockText.length);
        streamText = lastBlockText + suffix;
    } else {
        streamText = text;
    }
    // ... flush
}

Bug 5: Idle-timeout final text discarded by over-broad skip condition (Fix 6)

Root cause: When the streaming card idles out (~10 minutes, FEISHU_QUICK_ACTION_CARD_TTL_MS = 600000), streamingClosedForReply becomes true. The skipTextForClosedStreamingFinal condition then prevents delivery of ALL final texts, even those never delivered before:

const skipTextForClosedStreamingFinal = info?.kind === "final" && hasText && 
    streamingClosedForReply && !streamingCloseErroredForReply && 
    streamingEnabled && useCard;

Fix 6 (~line 1451): Add deliveredFinalTexts.has(text) guard:

const skipTextForClosedStreamingFinal = info?.kind === "final" && hasText && 
    streamingClosedForReply && !streamingCloseErroredForReply && 
    streamingEnabled && useCard && deliveredFinalTexts.has(text);

New final texts (never delivered before) fall through to the non-streaming sendChunkedTextReply path.


Bug 6: Stale pendingText overwrites final card content (Fix 7)

Root cause: FeishuStreamingSession.update uses this.pendingText ?? this.state.currentText as the merge input. When a short block update (e.g., tool error message) gets throttled via schedulePendingFlush, this.pendingText retains the stale content. The next streaming.update call reads this stale pendingText instead of the reset currentText:

async update(text) {
    // ...
    const mergedInput = mergeStreamingText(
        this.pendingText ?? this.state.currentText,  // BUG: stale pendingText!
        text
    );

If mergeStreamingText(staleText, finalText) finds no overlap, it concatenates them. In the worst case, mergedInput === currentText causes streaming.update to return without any card update.

Fix 7 (~line 1276): Clear pendingText and flush timer before each streaming update:

// In flushStreamingCardUpdate:
if (streaming.state) streaming.state.currentText = "";
streaming.pendingText = null;
if (streaming.flushTimer) { clearTimeout(streaming.flushTimer); streaming.flushTimer = null; }
await streaming.update(combined);

Files Affected

  • node_modules/@openclaw/feishu/dist/monitor.account-BJ8FoDGL.js (all 6 fixes)

How to Reproduce

Bug 5 (idle timeout): Generate a response where AI thinks/uses tools for >10 minutes between updates. The final text will not appear in the card.

Bug 6 (pendingText stale): Use a REACT agent that calls a tool and the tool fails. The error block update (short text) gets throttled, pendingText preserves the error. Final text either concatenates with error or is completely lost.

Bugs 1-4 (REACT content accumulation): Use a REACT agent that emits multiple tool call blocks followed by a final response. The card will show duplicate or concatenated content.

Impact

Users of Feishu/Lark see incorrect or incomplete card content, reducing trust in automated responses. Critical information (trading decisions, customer analyses) may be silently lost.

extent analysis

TL;DR

Apply the provided fixes (1-7) to the monitor.account-BJ8FoDGL.js file in @openclaw/feishu to resolve the Feishu streaming card content delivery issues.

Guidance

  • Review and apply each of the seven fixes to the specified lines in the monitor.account-BJ8FoDGL.js file.
  • Verify that the fixes resolve the issues by testing the scenarios described in the "How to Reproduce" section.
  • Ensure that the FEISHU_QUICK_ACTION_CARD_TTL_MS timeout value is reasonable for the application's use case.
  • Consider adding additional logging or monitoring to detect and handle similar issues in the future.

Example

No additional code snippet is provided, as the fixes are already included in the issue description.

Notes

The provided fixes address specific bugs in the monitor.account-BJ8FoDGL.js file. However, it is essential to thoroughly test the changes to ensure they do not introduce new issues or affect other parts of the application.

Recommendation

Apply the provided fixes (1-7) to the monitor.account-BJ8FoDGL.js file, as they address the root causes of the identified bugs and should resolve the content delivery issues.

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