fix(telegram): prevent silent message loss across all streamMode settings#19041
Merged
obviyus merged 7 commits intoopenclaw:mainfrom Feb 20, 2026
Merged
fix(telegram): prevent silent message loss across all streamMode settings#19041obviyus merged 7 commits intoopenclaw:mainfrom
obviyus merged 7 commits intoopenclaw:mainfrom
Conversation
|
@greptileai please review |
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
28d5adb to
97a87a7
Compare
Wrap fallback delivery in try/finally so clearDraftPreviewIfNeeded() runs even if the fallback deliverReplies() call itself throws. Prevents orphaned preview messages in double-failure scenarios. Addresses Codex 5.3 review feedback.
97a87a7 to
8289833
Compare
Contributor
anisoptera
pushed a commit
to anisoptera/openclaw
that referenced
this pull request
Feb 20, 2026
…ings (openclaw#19041) Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: 8289833 Co-authored-by: mudrii <[email protected]> Co-authored-by: obviyus <[email protected]> Reviewed-by: @obviyus
vincentkoc
pushed a commit
that referenced
this pull request
Feb 21, 2026
…ings (#19041) Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: 8289833 Co-authored-by: mudrii <[email protected]> Co-authored-by: obviyus <[email protected]> Reviewed-by: @obviyus
vincentkoc
pushed a commit
that referenced
this pull request
Feb 21, 2026
…ings (#19041) Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: 8289833 Co-authored-by: mudrii <[email protected]> Co-authored-by: obviyus <[email protected]> Reviewed-by: @obviyus
mmyyfirstb
pushed a commit
to mmyyfirstb/openclaw
that referenced
this pull request
Feb 21, 2026
…ings (openclaw#19041) Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: 8289833 Co-authored-by: mudrii <[email protected]> Co-authored-by: obviyus <[email protected]> Reviewed-by: @obviyus
6 tasks
This was referenced Feb 22, 2026
zooqueen
pushed a commit
to hanzoai/bot
that referenced
this pull request
Mar 6, 2026
…ings (openclaw#19041) Merged via /review-pr -> /prepare-pr -> /merge-pr. Prepared head SHA: 8289833 Co-authored-by: mudrii <[email protected]> Co-authored-by: obviyus <[email protected]> Reviewed-by: @obviyus
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Telegram outbound messages are silently lost across all three
streamModesettings (off,partial,block). This PR fixes the dispatch pipeline to ensure responses always reach the user — or at minimum, a fallback "No response generated" message is sent instead of silent loss.Closes #19001
Related: #18244, #8691, #16604, #17668, #18195, #18859
What Changed and Why
1. Fixed
disableBlockStreamingevaluation order (bot-message-dispatch.ts)Before: The
disableBlockStreamingvariable was computed as:When
streamMode === "off",draftStreamisundefined(sincecanStreamDraftis false). The ternary chain evaluatesdraftStreamfirst — which isundefined(falsy) — so it falls through to thestreamMode === "off"check. However, because this is a three-way ternary, the firsttypeof telegramCfg.blockStreaming === "boolean"branch can take priority, and whenblockStreamingis not explicitly set, the result isundefined(nottrue). Code paths downstream that checkif (disableBlockStreaming)don't trigger onundefined, allowing block streaming logic to run even inoffmode.After:
streamMode === "off"is checked first, guaranteeingdisableBlockStreaming = true:Why: This eliminates an entire class of
off-mode message loss where block streaming logic inadvertently runs.2. Added
failedDeliveriescounter todeliveryState(bot-message-dispatch.ts)Before: The fallback "No response generated" message only fired when
deliveryState.skippedNonSilent > 0. IfdeliverReplies()threw an error (network failure, 403 bot blocked, etc.), the failure was swallowed by theonErrorcallback and logged — butskippedNonSilentwasn't incremented, so no fallback was triggered. The user saw nothing.After: A new
failedDeliveriescounter is incremented in theonErrorcallback. The fallback condition now checks:Why: Delivery failures are now recoverable. If the primary delivery path fails, the fallback still fires.
3. Moved
draftStream.clear()out of thefinallyblock (bot-message-dispatch.ts)Before: The
finallyblock unconditionally called:When tool errors arrived as
isErrorpayloads, they didn't finalize the preview (finalizedViaPreviewMessagestayedfalse). Soclear()deleted the draft message — even though it contained the agent's partially-streamed response that the user was actively reading.After: The
finallyblock only callsstop(). The cleanup is moved to aclearDraftPreviewIfNeeded()helper that runs after the fallback delivery logic:The helper checks
finalizedViaPreviewMessageand only deletes the preview if it wasn't finalized as the actual response:Why: This prevents the race condition where
clear()deletes a message the user can see, while still cleaning up stale previews in cases like NO_REPLY, error-only responses, or media responses.4. Added delivery confirmation logging (
bot-message-dispatch.ts,delivery.ts,draft-stream.ts)Before: No logging for successful
sendMessagecalls ordeleteMessagein draft cleanup. When messages vanished, there was no way to distinguish "message never sent" from "message sent then deleted" from "Telegram API silently dropped it."After:
delivery.ts: Logstelegram sendMessage ok chat=X message=Yon successful sendsbot-message-dispatch.ts: Logstelegram: finalized response via preview edit (messageId=X)andtelegram: X reply delivered to chat Ydraft-stream.ts: Logstelegram stream preview deleted (chat=X, message=Y)inclear()Why: These logs make it possible to diagnose message loss by comparing what was sent vs what was deleted vs what the user sees.
Changes by File
src/telegram/bot-message-dispatch.ts(+67/-17)disableBlockStreamingevaluation order (off mode now always returnstrue)failedDeliveriestodeliveryStateclearDraftPreviewIfNeeded()helperclear()out offinallyblock to after fallback logiconErrorcallback now incrementsfailedDeliveriesfailedDeliveries > 0logVerbose()for delivery success, failure, and preview edit outcomessrc/telegram/bot-message-dispatch.test.ts(+192/-1)8 new test cases covering the previously-untested failure modes:
clears preview for error-only finals— When all final payloads are errors, preview must be cleaned up (not left orphaned)clears preview after media final delivery— Media responses can't finalize via preview edit; preview must be deletedclears stale preview when response is NO_REPLY— Preview contains stale partial text that must be removedfalls back when all finals are skipped and clears preview— Skipped finals trigger fallback + cleanupsends fallback and clears preview when deliver throws— Network failures now trigger fallback instead of silent losssends fallback in off mode when deliver throws— Same as above but specifically forstreamMode: "off"(no draft stream)handles error block + response final— Error notifications don't overwrite the response (error goes viadeliverReplies, response finalizes preview)supports concurrent dispatches with independent previews— Two simultaneous dispatches don't interfere with each other's preview cleanupsrc/telegram/bot/delivery.ts(+2)runtime.log?.(...)after successfulsendMessagecalls for delivery confirmationsrc/telegram/draft-stream.ts(+1)params.log?.(...)inclear()after successfuldeleteMessagefor cleanup tracingHow This Relates to Other Open PRs
There are 6 open PRs touching Telegram message dispatch. This PR is complementary — it fixes root causes that none of them address:
disableBlockStreamingundefined in off modefinallyblock deletes unfinalized previewclearDraftPreviewIfNeeded()approach, but creates orphan message risk. Our approach is safer: cleanup always runs, just after fallback.What This PR Does NOT Fix
sendMessagecalls — The new logging will help diagnose whether this actually happensTesting
Automated (22 tests total, all pass)
Manual Verification Checklist
streamMode: "off"— messages deliver after tool errorsstreamMode: "partial"— streaming + final works, errors don't overwritestreamMode: "block"— block streaming works, long responses surviveChange Type
Scope
Security Impact
Compatibility
Fully backward compatible. No config schema changes, no new dependencies. The logging additions are gated behind existing
logVerbose/runtime.logpatterns.Failure Recovery
If this fix introduces a regression, the worst case is the same as the current behavior (silent message loss). The added logging makes it easier to diagnose and faster to iterate.
AI-assisted (Claude Opus 4.6). 22 tests pass. 4 files changed, +245/-17 lines.
Greptile Summary
This PR fixes silent Telegram message loss across all three
streamModesettings (off,partial,block) through four targeted changes to the dispatch pipeline.Key changes:
disableBlockStreamingevaluation order:streamMode === "off"is now checked first, guaranteeingtrueregardless oftelegramCfg.blockStreaming. This is a breaking behavioral change — previouslyblockStreaming: trueconfig took precedence overstreamMode: "off"; nowstreamMode: "off"always wins. A test (disables block streaming when streamMode is off even if blockStreaming config is true) was added to document and enforce this new behavior.failedDeliveriescounter: Added todeliveryStateand incremented inonError. The fallback "No response generated" condition now also fires whenfailedDeliveries > 0, covering network failures and 403 errors that previously resulted in silent loss.clearDraftPreviewIfNeeded()helper:draftStream.clear()moved out of the mainfinallyblock into a new helper called after fallback logic, wrapped in its owntry/finally. This prevents premature preview deletion for error payloads and media responses, while still guaranteeing cleanup in all non-finalized cases.sendMessagesuccess anddeleteMessageevents now logged viaruntime.logandparams.log, making message-loss diagnosis tractable.The 8 new tests cover previously-untested failure modes and confirm correct behavior. The approach is complementary to other open PRs and does not introduce new dependencies or config changes.
Confidence Score: 4/5
bot-message-dispatch.tsarounddisableBlockStreamingpriority.Last reviewed commit: 5c493b2