Skip to content

fix(telegram): Fixed duplicate resend/delete behavior in partial streaming mode.#17766

Closed
v8hid wants to merge 3 commits intoopenclaw:mainfrom
v8hid:fix/telegram-duplicate-reply
Closed

fix(telegram): Fixed duplicate resend/delete behavior in partial streaming mode.#17766
v8hid wants to merge 3 commits intoopenclaw:mainfrom
v8hid:fix/telegram-duplicate-reply

Conversation

@v8hid
Copy link
Copy Markdown

@v8hid v8hid commented Feb 16, 2026

Summary

  • Problem: In partial draft streaming, Telegram finalization would attempt editMessageText with unchanged content.
  • Why it matters: Telegram reject same-content edits (message is not modified), which trigger failover cleanup/send and create duplicate visible messages plus producing error logs.
  • What changed: Added lastAppliedText() tracking in src/telegram/draft-stream.ts and used it in src/telegram/bot-message-dispatch.ts to short-circuit unchanged final edits when no mutation is needed.
  • What changed: Kept final edit behavior for real mutations (formatting pass, inline buttons, linkPreview=false) and expanded tests in src/telegram/draft-stream.test.ts and src/telegram/bot-message-dispatch.test.ts.

Change Type

  • Bug fix

Scope (select all touched areas)

  • Integrations

Related Fix

Note: Commit ac2ede5 by @steipete already treats message is not modified API errors as success, which prevents the failover/duplicate behavior. This PR adds a complementary optimization that prevents the unnecessary API call from being attempted in the first place by tracking lastAppliedText() and short-circuiting when no edit is needed.

Both fixes work together:

  • ac2ede5bb: Reactive safety net at the API error handling layer
  • This PR: Proactive optimization at the logic layer to avoid the no-op API call entirely
    Both fixes work together:
  • ac2ede5bb: Reactive safety net at the API error handling layer
  • This PR: Proactive optimization at the logic layer to avoid the no-op API call entirely

User-visible / Behavior Changes

  • Telegram streamed replies no longer attempt redundant same-content final edits.
  • Reduces duplicate resend behavior after finalization failover in partial streaming mode.
  • no error fatigue on logs

Security Impact (required)

  • New permissions/capabilities? (No)
  • Secrets/tokens handling changed? (No)
  • New/changed network calls? (No)
  • Command/tool execution surface changed? (No)
  • Data access scope changed? (No)

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: Node + pnpm (repo default)
  • Model/provider: gpt-5.3-codex
  • Integration/channel (if any): Telegram
  • Relevant config (redacted): channels.telegram.streamMode=partial,
    channels.telegram.blockStreaming=false

Steps

  1. Send a message that produces a streamed preview and a final text equal to that preview.
  2. Finalization path reaches preview edit step.
  3. Observe edit/failover behavior.

Expected

  • One final visible message, no redundant resend.

Actual

  • Before fix: message streamed and edited -> deleted -> resend same message again.
  • After fix: finalize existing preview directly when no mutation is needed.

Evidence

Attach at least one:

  • Failing test/log before + passing after

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios:
    • Ran pnpm test src/telegram/bot-message-dispatch.test.ts (8/8 passing).
    • Verified new branches in src/telegram/bot-message-dispatch.test.ts:
      • skip edit when preview equals final
      • keep edit when formatting/buttons/linkPreview mutation is required
    • Live end-to-end Telegram run against real Bot API in this pass.
  • Edge cases checked:
    • unchanged final text
    • markdown formatting pass required
    • inline buttons mutation
    • linkPreview=false mutation

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly:
    • Revert this commit, or temporarily set channels.telegram.streamMode=off.
  • Files/config to restore:
    • src/telegram/bot-message-dispatch.ts
    • src/telegram/draft-stream.ts
  • Known bad symptoms reviewers should watch for:
    • Unexpected duplicate message sends after finalization.

Greptile Summary

This PR adds a proactive optimization to prevent unnecessary Telegram API calls when finalizing streamed messages. It tracks the last successfully applied text in draft streams and short-circuits final edits when the content hasn't changed, while preserving edits for legitimate mutations (markdown formatting, inline buttons, linkPreview settings).

Key changes:

  • Added lastAppliedText() method to TelegramDraftStream interface that tracks the last successfully applied text (stored only on successful API calls, not on failed edits)
  • Added early-return logic in bot-message-dispatch.ts that compares preview text with final text and skips edit when no changes or mutations are needed
  • Checks for formatting differences using renderTelegramHtmlText() to ensure markdown formatting passes still trigger edits
  • Comprehensive test coverage for skip/keep scenarios including edge cases

This complements commit ac2ede5 which treats MESSAGE_NOT_MODIFIED errors as success at the API layer. Together they provide defense-in-depth: this PR prevents unnecessary calls, while ac2ede5 provides a safety net if they occur.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The implementation is well-designed with proper separation of concerns. The lastAppliedText tracking correctly distinguishes between attempted vs. successful edits, comprehensive test coverage validates all edge cases (formatting passes, button mutations, linkPreview changes), and the logic correctly handles the mismatch between plain-text draft previews and HTML-formatted final edits. The change is purely additive (early-return optimization) with no modification to existing success paths, and it works in tandem with existing error handling (ac2ede5) for defense-in-depth.
  • No files require special attention

Last reviewed commit: 8a34945

(2/5) Greptile learns from your feedback when you react with thumbs up/down!

@openclaw-barnacle openclaw-barnacle bot added channel: telegram Channel integration: telegram size: S labels Feb 16, 2026
…aming mode.

 
Instead of trying to edit a preview message with identical content, finalize the
existing preview in place. Editing with the same content can return a Telegram
API error, which then triggers failover cleanup/send and may duplicate output.

Retain final edit/send for real mutations (formatting pass, buttons, linkPreview)
and add dispatch tests for each branch.
@v8hid v8hid force-pushed the fix/telegram-duplicate-reply branch from 0da0427 to 665fbe8 Compare February 16, 2026 20:43
@steipete steipete closed this Feb 16, 2026
@steipete steipete reopened this Feb 17, 2026
@openclaw-barnacle openclaw-barnacle bot added channel: bluebubbles Channel integration: bluebubbles channel: discord Channel integration: discord channel: googlechat Channel integration: googlechat channel: imessage Channel integration: imessage channel: matrix Channel integration: matrix channel: msteams Channel integration: msteams channel: nextcloud-talk Channel integration: nextcloud-talk channel: nostr Channel integration: nostr channel: signal Channel integration: signal channel: slack Channel integration: slack channel: tlon Channel integration: tlon channel: voice-call Channel integration: voice-call channel: whatsapp-web Channel integration: whatsapp-web channel: zalo Channel integration: zalo channel: zalouser Channel integration: zalouser app: web-ui App: web-ui gateway Gateway runtime extensions: diagnostics-otel Extension: diagnostics-otel extensions: llm-task Extension: llm-task extensions: lobster Extension: lobster extensions: memory-lancedb Extension: memory-lancedb cli CLI command changes labels Feb 17, 2026
@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

24 similar comments
@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated commits). Please recreate the PR from a clean branch.

@v8hid
Copy link
Copy Markdown
Author

v8hid commented Feb 17, 2026

Rebased cleanly onto main — turns out merging main into a feature branch makes the bot think you're trying to smuggle 200 unrelated commits into your PR. Lesson learned. 2 clean commits now, as intended.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling app: web-ui App: web-ui channel: bluebubbles Channel integration: bluebubbles channel: discord Channel integration: discord channel: feishu Channel integration: feishu channel: googlechat Channel integration: googlechat channel: imessage Channel integration: imessage channel: irc channel: matrix Channel integration: matrix channel: msteams Channel integration: msteams channel: nextcloud-talk Channel integration: nextcloud-talk channel: nostr Channel integration: nostr channel: signal Channel integration: signal channel: slack Channel integration: slack channel: telegram Channel integration: telegram channel: tlon Channel integration: tlon channel: twitch Channel integration: twitch channel: voice-call Channel integration: voice-call channel: whatsapp-web Channel integration: whatsapp-web channel: zalo Channel integration: zalo channel: zalouser Channel integration: zalouser cli CLI command changes commands Command implementations docker Docker and sandbox tooling extensions: device-pair extensions: diagnostics-otel Extension: diagnostics-otel extensions: llm-task Extension: llm-task extensions: lobster Extension: lobster extensions: memory-lancedb Extension: memory-lancedb extensions: phone-control gateway Gateway runtime scripts Repository scripts size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants