Bug Description
Multiple streaming cards are created for a single conversational turn in the Feishu channel, resulting in duplicate messages being sent to users.
Root Cause
The issue lies in the state management of streaming and streamingStartPromise within /home/jonathan/.npm-global/lib/node_modules/openclaw/extensions/feishu/src/reply-dispatcher.ts.
Execution Flow:
onReplyStart calls startStreaming(), initiating the first streaming card (Card 1).
- During the reply lifecycle (e.g., receiving tool results or multiple chunks), the
deliver callback is invoked.
- If
streaming becomes inactive or is closed, closeStreaming() is called, which sets:
streaming = null
streamingStartPromise = null
- Subsequent calls to
startStreaming() (from onReplyStart for new chunks or logic handling tool outputs) check these guards. Since they are null, the check passes, creating a NEW FeishuStreamingSession (Card 2).
- This repeats if the cycle happens again, creating Card 3, etc.
Evidence
Log Entries (journalctl):
14:57:29 Started streaming cardId=7617001869138906054 (Session A)
14:57:32 Started streaming cardId=7617001879461661637 (Session B)
14:57:33 Started streaming cardId=7617001885241887708 (Session C)
14:57:52 Closed streaming cardId=7617001869138906054 (Session A closed 23s late)
User Observation:
Screenshot shows two similar messages with slight differences (second missing prefix "Let me check..."), confirming that multiple streaming sessions are being initialized and finalized for what is likely intended to be a single conversational turn.
Proposed Fix
Implement a synchronous guard flag (e.g., starting) to strictly prevent re-entry into startStreaming() while the async initialization is pending or while a session is active for that specific reply context.
let starting = false;
const startStreaming = () => {
// Add 'starting' to the guard condition
if (!streamingEnabled || streamingStartPromise || streaming || starting) {
return;
}
starting = true; // Set guard immediately
streamingStartPromise = (async () => {
try {
// ... initialization code ...
} finally {
starting = false; // Release guard only after completion/error
}
})();
};
This ensures that closeStreaming() clearing streaming does not inadvertently allow a race condition to create a duplicate session before the current reply sequence is fully finished.
Environment
- OpenClaw Version: v2026.3.12
- Channel: Feishu
- File:
extensions/feishu/src/reply-dispatcher.ts
- Function:
startStreaming(), closeStreaming()
Related Issues
Bug Description
Multiple streaming cards are created for a single conversational turn in the Feishu channel, resulting in duplicate messages being sent to users.
Root Cause
The issue lies in the state management of
streamingandstreamingStartPromisewithin/home/jonathan/.npm-global/lib/node_modules/openclaw/extensions/feishu/src/reply-dispatcher.ts.Execution Flow:
onReplyStartcallsstartStreaming(), initiating the first streaming card (Card 1).delivercallback is invoked.streamingbecomes inactive or is closed,closeStreaming()is called, which sets:streaming = nullstreamingStartPromise = nullstartStreaming()(fromonReplyStartfor new chunks or logic handling tool outputs) check these guards. Since they are null, the check passes, creating a NEWFeishuStreamingSession(Card 2).Evidence
Log Entries (journalctl):
User Observation:
Screenshot shows two similar messages with slight differences (second missing prefix "Let me check..."), confirming that multiple streaming sessions are being initialized and finalized for what is likely intended to be a single conversational turn.
Proposed Fix
Implement a synchronous guard flag (e.g.,
starting) to strictly prevent re-entry intostartStreaming()while the async initialization is pending or while a session is active for that specific reply context.This ensures that
closeStreaming()clearingstreamingdoes not inadvertently allow a race condition to create a duplicate session before the current reply sequence is fully finished.Environment
extensions/feishu/src/reply-dispatcher.tsstartStreaming(),closeStreaming()Related Issues