-
-
Notifications
You must be signed in to change notification settings - Fork 69.3k
Telegram streaming "partial" creates duplicate messages due to generation counter race #39795
Description
Description
When using streaming: "partial" on Telegram channels, users see duplicate messages: the response appears twice briefly, then one copy is deleted. This happens consistently, especially on mobile where both notifications are visible.
Environment
- OpenClaw version:
2026.3.2 - Platform: Linux (WSL2)
- Telegram channel with 5 bot accounts
streaming: "partial"on all accounts
Steps to Reproduce
- Configure a Telegram account with
"streaming": "partial"inopenclaw.json - Send a message to the bot from Telegram mobile
- Observe: the response message appears, then a second identical message appears, then the first one is deleted
Root Cause Analysis
Traced through pi-embedded-CtM2Mrrj.js:
- Stream starts → Preview message fix: add @lid format support and allowFrom wildcard handling #1 (M1) sent via
sendMessage()(generation=0) onAssistantMessageStartfires (new response segment):- M1 is saved to
archivedAnswerPreviews forceNewMessage()incrementsgenerationto 1
- M1 is saved to
- Next stream partial arrives →
sendOrEditStreamMessage()detectsgenerationchanged → sends a newsendMessage()→ Preview Login fails with 'WebSocket Error (socket hang up)' ECONNRESET #2 (M2) created — duplicate now visible - Final delivery →
deliverLaneText()callsconsumeArchivedAnswerPreviewForFinal()→ tries toeditMessageTexton M1 → fails (orphaned) → falls back tosendPayload()(yet another message) - Cleanup →
deleteMessage(M1)— user sees the "correction"
The core issue is forceNewMessage() being called in onAssistantMessageStart even when the response is part of the same logical reply. This orphans the current preview and forces creation of a new one, leaving two messages visible until cleanup.
Workaround
Setting "streaming": "off" on all Telegram accounts eliminates the issue entirely. This bypasses the preview/edit pipeline.
"telegram": {
"streaming": "off"
}Suggested Fix
onAssistantMessageStart should not call forceNewMessage() if the current preview message is still the active one for the same reply context. The generation counter should only increment when a genuinely new message thread is needed, not on every assistant segment boundary.
Alternatively, deliverLaneText() should check the active stream preview (M2) before falling back to sendPayload(), instead of only trying the archived preview (M1).