Skip to content

fix(feishu): prevent duplicate message after streaming card close (#67791)#68491

Merged
vincentkoc merged 2 commits into
openclaw:mainfrom
MoerAI:fix/feishu-streaming-duplicate
Apr 25, 2026
Merged

fix(feishu): prevent duplicate message after streaming card close (#67791)#68491
vincentkoc merged 2 commits into
openclaw:mainfrom
MoerAI:fix/feishu-streaming-duplicate

Conversation

@MoerAI

@MoerAI MoerAI commented Apr 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Feishu sends a duplicate text/card message after streaming completes because the onIdle-triggered closeStreaming() does not record the streamed content in deliveredFinalTexts. The subsequent final delivery bypasses the inactive streaming guard and sends the same content again.

Root Cause

When onIdle fires before the final delivery arrives:

  1. closeStreaming() finalizes the streaming card with the accumulated text
  2. streaming is set to null
  3. The final delivery arrives, but streaming?.isActive() returns false
  4. The code falls through to the non-streaming path and sends a redundant message
  5. deliveredFinalTexts does not contain the streamed text (only added at line 419 inside the isActive() guard), so the duplicate check passes

Changes

  • extensions/feishu/src/reply-dispatcher.ts: Add streamText to deliveredFinalTexts in closeStreaming() before clearing state, so the duplicate-final check in deliver() can skip the redundant text delivery.

Test

All 30 reply-dispatcher tests pass (pnpm test extensions/feishu/src/reply-dispatcher.test.ts). 2 pre-existing failures in other Feishu test files (media upload mock, webhook ECONNRESET) are unrelated.

Closes #67791

@openclaw-barnacle openclaw-barnacle Bot added channel: feishu Channel integration: feishu size: XS labels Apr 18, 2026
@greptile-apps

greptile-apps Bot commented Apr 18, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a duplicate-message bug in the Feishu reply dispatcher where onIdle closes the streaming card (via closeStreaming()) before the final delivery arrives, causing the final text to be sent again through the non-streaming path because deliveredFinalTexts was only populated inside the streaming?.isActive() guard. The fix adds streamText to deliveredFinalTexts inside closeStreaming() before state is cleared, so the dedup check in deliver() can catch the subsequent redundant final delivery.

Confidence Score: 5/5

Safe to merge — the fix is logically correct, minimal, and consistent with how deliveredFinalTexts is used elsewhere; the only finding is a missing regression test.

All findings are P2. The fix correctly captures streamText before state is cleared, the if (streamText) guard prevents spurious empty-string entries, and adding to a Set in the normal streaming path (where deliveredFinalTexts.add(text) is also called at line 425) is idempotent.

No files require special attention.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/feishu/src/reply-dispatcher.ts
Line: 317-319

Comment:
**Missing regression test for the fixed race sequence**

The fix correctly adds `streamText` to `deliveredFinalTexts` in `closeStreaming()`, but there is no new test covering the specific sequence: `onPartialReply``onIdle` (closes streaming) → `deliver({…}, { kind: "final" })`. The existing tests cover duplicate `final` deliveries in back-to-back calls, but not the `onIdle`-before-`final` timing described in the PR description. Adding a test that mimics this sequence would guard against the regression recurring without the dedup logic breaking in subtler ways.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(feishu): prevent duplicate message a..." | Re-trigger Greptile

Comment thread extensions/feishu/src/reply-dispatcher.ts
@prtags

prtags Bot commented Apr 23, 2026

Copy link
Copy Markdown

Related work from PRtags group moving-rooster-bp2l

Title: Open PR candidate: Feishu streaming duplicate messages

Number Title
#66148 fix(feishu): remove streaming card update throttle to prevent duplicates
#68491* fix(feishu): prevent duplicate message after streaming card close (#67791)

* This PR

MoerAI and others added 2 commits April 24, 2026 23:08
…enclaw#67791)

When onIdle closed the streaming card before the final delivery arrived, the streamed text was not tracked in deliveredFinalTexts. The subsequent final payload bypassed the streaming?.isActive() guard (already closed) and fell through to the non-streaming path, sending the same content as a redundant text/card message. Track raw streamText in deliveredFinalTexts when closeStreaming finalizes the card so the duplicate-final check catches it.
@vincentkoc vincentkoc force-pushed the fix/feishu-streaming-duplicate branch from 4c58143 to a20dfdc Compare April 25, 2026 06:09
@vincentkoc vincentkoc merged commit 2aa313c into openclaw:main Apr 25, 2026
64 checks passed
Angfr95 pushed a commit to Angfr95/openclaw that referenced this pull request Apr 25, 2026
…enclaw#67791) (openclaw#68491)

* fix(feishu): prevent duplicate message after streaming card close (openclaw#67791)

When onIdle closed the streaming card before the final delivery arrived, the streamed text was not tracked in deliveredFinalTexts. The subsequent final payload bypassed the streaming?.isActive() guard (already closed) and fell through to the non-streaming path, sending the same content as a redundant text/card message. Track raw streamText in deliveredFinalTexts when closeStreaming finalizes the card so the duplicate-final check catches it.

* test(feishu): cover idle streaming final dedupe

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
@MoerAI MoerAI deleted the fix/feishu-streaming-duplicate branch April 27, 2026 11:03
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
…enclaw#67791) (openclaw#68491)

* fix(feishu): prevent duplicate message after streaming card close (openclaw#67791)

When onIdle closed the streaming card before the final delivery arrived, the streamed text was not tracked in deliveredFinalTexts. The subsequent final payload bypassed the streaming?.isActive() guard (already closed) and fell through to the non-streaming path, sending the same content as a redundant text/card message. Track raw streamText in deliveredFinalTexts when closeStreaming finalizes the card so the duplicate-final check catches it.

* test(feishu): cover idle streaming final dedupe

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…enclaw#67791) (openclaw#68491)

* fix(feishu): prevent duplicate message after streaming card close (openclaw#67791)

When onIdle closed the streaming card before the final delivery arrived, the streamed text was not tracked in deliveredFinalTexts. The subsequent final payload bypassed the streaming?.isActive() guard (already closed) and fell through to the non-streaming path, sending the same content as a redundant text/card message. Track raw streamText in deliveredFinalTexts when closeStreaming finalizes the card so the duplicate-final check catches it.

* test(feishu): cover idle streaming final dedupe

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
globalcaos pushed a commit to globalcaos/tinkerclaw that referenced this pull request May 13, 2026
…enclaw#67791) (openclaw#68491)

* fix(feishu): prevent duplicate message after streaming card close (openclaw#67791)

When onIdle closed the streaming card before the final delivery arrived, the streamed text was not tracked in deliveredFinalTexts. The subsequent final payload bypassed the streaming?.isActive() guard (already closed) and fell through to the non-streaming path, sending the same content as a redundant text/card message. Track raw streamText in deliveredFinalTexts when closeStreaming finalizes the card so the duplicate-final check catches it.

* test(feishu): cover idle streaming final dedupe

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
…enclaw#67791) (openclaw#68491)

* fix(feishu): prevent duplicate message after streaming card close (openclaw#67791)

When onIdle closed the streaming card before the final delivery arrived, the streamed text was not tracked in deliveredFinalTexts. The subsequent final payload bypassed the streaming?.isActive() guard (already closed) and fell through to the non-streaming path, sending the same content as a redundant text/card message. Track raw streamText in deliveredFinalTexts when closeStreaming finalizes the card so the duplicate-final check catches it.

* test(feishu): cover idle streaming final dedupe

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
…enclaw#67791) (openclaw#68491)

* fix(feishu): prevent duplicate message after streaming card close (openclaw#67791)

When onIdle closed the streaming card before the final delivery arrived, the streamed text was not tracked in deliveredFinalTexts. The subsequent final payload bypassed the streaming?.isActive() guard (already closed) and fell through to the non-streaming path, sending the same content as a redundant text/card message. Track raw streamText in deliveredFinalTexts when closeStreaming finalizes the card so the duplicate-final check catches it.

* test(feishu): cover idle streaming final dedupe

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
…enclaw#67791) (openclaw#68491)

* fix(feishu): prevent duplicate message after streaming card close (openclaw#67791)

When onIdle closed the streaming card before the final delivery arrived, the streamed text was not tracked in deliveredFinalTexts. The subsequent final payload bypassed the streaming?.isActive() guard (already closed) and fell through to the non-streaming path, sending the same content as a redundant text/card message. Track raw streamText in deliveredFinalTexts when closeStreaming finalizes the card so the duplicate-final check catches it.

* test(feishu): cover idle streaming final dedupe

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: feishu Channel integration: feishu size: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Feishu Duplicate message sent after streaming card completes — blockStreamingCoalesce regression since v2026.4.12

2 participants