openclaw - ✅(Solved) Fix [Bug]: Telegram streaming.mode="partial" completely disabled when replyToMode is not "off" [2 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#73505Fetched 2026-04-29 06:19:05
View on GitHub
Comments
1
Participants
2
Timeline
5
Reactions
0
Author
Timeline (top)
cross-referenced ×3commented ×1referenced ×1

When channels.telegram.replyToMode is set to "first" (or any non-"off" value), Telegram streaming preview (streaming.mode: "partial") is completely disabled. The bot produces no visible output during generation, then delivers all messages at once when the run completes.

Root Cause

In extensions/telegram/src/bot-message-dispatch.ts (dist: bot-msflwCEW.js in 2026.4.26):

const hasNativeQuoteReply = replyToMode !== "off" && Object.keys(replyQuoteByMessageId).length > 0;
const canStreamAnswerDraft = previewStreamingEnabled && !hasNativeQuoteReply && ...

When replyToMode !== "off", the handler always calls addTelegramNativeQuoteCandidate(replyQuoteByMessageId, msg.message_id, ...), which unconditionally populates replyQuoteByMessageId. This makes Object.keys(replyQuoteByMessageId).length > 0 always true, so hasNativeQuoteReply is always true, and canStreamAnswerDraft is always false.

The intended reason to disable streaming is when the reply contains a Telegram quote (i.e. reply_parameters.quote), because that parameter cannot be passed via editMessageText. A plain reply_to_message_id is fully compatible with streaming — it is passed on the first sendMessage call and subsequent editMessageText calls work fine. The code already correctly passes draftReplyToMessageId into createTelegramDraftStream, confirming the two features are designed to coexist.

Fix Action

Fix / Workaround

In extensions/telegram/src/bot-message-dispatch.ts (dist: bot-msflwCEW.js in 2026.4.26):

PR fix notes

PR #73534: fix(telegram): scope native quote streaming guard to real quotes (#73505)

Description (problem / solution / changelog)

What

Fixes #73505. When channels.telegram.replyToMode is set to anything other than "off", partial-stream preview is silently disabled for plain replies even though editMessageText round-trips with reply_to_message_id unchanged. The result is that operators with replyToMode: "first" see no streaming preview at all — text appears only after the run completes.

Root cause

bot-message-dispatch.ts builds a replyQuoteByMessageId map for native-quote candidate lookups during delivery, then computes:

const hasNativeQuoteReply =
  replyToMode !== "off" && Object.keys(replyQuoteByMessageId).length > 0;

Under replyToMode !== "off" the dispatcher unconditionally calls addTelegramNativeQuoteCandidate(replyQuoteByMessageId, ctxPayload.MessageSid ?? msg.message_id, ...) for the inbound message itself, so any inbound message with text populates the map and trips hasNativeQuoteReplycanStreamAnswerDraft = false → no preview lane.

The reason streaming is incompatible with native quote replies is the Telegram quote parameter, which editMessageText cannot re-send. Plain reply_to_message_id (no quote) does not have that constraint — and the dispatcher already passes draftReplyToMessageId into createTelegramDraftStream, which means the rest of the codepath is built to handle this case.

Fix

Tighten the guard to require an actual user-selected quote, matching the same replyQuoteText && replyQuoteMessageId != null pattern already used a few lines above to gate addTelegramNativeQuoteCandidate for explicit reply targets:

const hasNativeQuoteReply =
  replyToMode !== "off" && replyQuoteText != null && replyQuoteMessageId != null;

replyQuoteText is only set when ctxPayload.ReplyToIsQuote is true, and replyQuoteMessageId is only set when that quote points at a non-external resolvable message. Together they describe the only case where streaming truly cannot work.

Behavior preserved

  • Quote-bearing replies (ReplyToIsQuote=true with text + resolvable id) still take the non-streaming path — covered by the existing skips answer draft preview for same-chat selected quotes test.
  • replyQuoteByMessageId is still passed verbatim to deliverReplies for fallback handling — covered by the existing passes native quote candidates for current message replies and passes native quote candidates for explicit reply targets tests, which still assert the same map payload.

Test changes

  • passes native quote candidates for current message replies — flipped from asserting createTelegramDraftStream is NOT called to asserting it IS called. The map-payload assertion is unchanged: candidates are still attached for the eventual final delivery.
  • New regression test keeps answer draft preview for plain replies in groups with replyToMode set — pins #73505 behavior directly with replyToMode: "first" + streamMode: "partial" + plain inbound message → expects createTelegramDraftStream and draftStream.update("Hello").

Verified locally

npx oxlint extensions/telegram/src/bot-message-dispatch.ts extensions/telegram/src/bot-message-dispatch.test.ts
# Found 0 warnings and 0 errors.

npx vitest run extensions/telegram/src/bot-message-dispatch.test.ts
# Tests  109 passed (109)

npx vitest run extensions/telegram/src/bot/delivery.test.ts
# Tests  41 passed (41)

lobster-biscuit: 73505-replytomode-streaming-guard

Sign-Off:

  • I have read and agree to the OpenClaw Contributor License Agreement.

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • extensions/telegram/src/bot-message-dispatch.test.ts (modified, +45/-1)
  • extensions/telegram/src/bot-message-dispatch.ts (modified, +9/-1)

PR #39905: feat(telegram): scope reply and streaming settings per conversation

Description (problem / solution / changelog)

Summary

Describe the problem and fix in 2–5 bullets:

  • Problem: Telegram replyToMode and preview streaming were effectively account-wide defaults, so direct chats, groups, and topics could not choose different reply styles.
  • Why it matters: some users want quoted live replies, others want plain 1:1 live replies, and Telegram quoted streaming must not regress into the old preview-message + final-message + delete-preview behavior.
  • What changed: added Telegram replyToMode and streaming overrides to direct, groups, and topics; resolved those settings per conversation before dispatch; applied the same reply-threading override to native command replies; documented the new config surface.
  • What did NOT change (scope boundary): this PR does not add command-based preference persistence, UI controls, or non-Telegram channel changes.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #
  • Related #39880

User-visible / Behavior Changes

  • Telegram now accepts conversation-scoped replyToMode overrides under:
    • channels.telegram.direct.<id>.replyToMode
    • channels.telegram.groups.<id>.replyToMode
    • channels.telegram.groups.<id>.topics.<threadId>.replyToMode
    • channels.telegram.direct.<id>.topics.<threadId>.replyToMode
  • Telegram now accepts conversation-scoped preview streaming overrides under the matching direct/group/topic scopes.
  • Telegram auto replies now resolve quoted-vs-non-quoted behavior per conversation instead of forcing one account-wide default.
  • Telegram native command replies now honor the same per-conversation reply threading override.
  • Quoted DM live streaming stays attached to one reply message instead of creating a temporary preview message, then sending a second final message, then deleting the first one.

Security Impact (required)

  • New permissions/capabilities? (Yes/No) No
  • Secrets/tokens handling changed? (Yes/No) No
  • New/changed network calls? (Yes/No) No
  • Command/tool execution surface changed? (Yes/No) No
  • Data access scope changed? (Yes/No) No
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: Ubuntu on WSL2
  • Runtime/container: local Node/pnpm workspace
  • Model/provider: N/A (unit/integration-style Telegram channel tests)
  • Integration/channel (if any): Telegram
  • Relevant config (redacted): account-level channels.telegram.replyToMode/streaming plus scoped overrides under direct, groups, and topics

Steps

  1. Configure Telegram account defaults with quoted replies and live preview enabled.
  2. Add scoped overrides for one DM / one group / one topic with different replyToMode or streaming values.
  3. Send Telegram replies through the message processor and native command path.
  4. Exercise quoted DM preview streaming.

Expected

  • Scoped overrides win over account defaults for the targeted DM/group/topic.
  • Quoted DM live preview stays in a single reply message and does not create a second final message.

Actual

  • Before this change, Telegram always used account-level replyToMode / streaming defaults.
  • Before this change, quoted DM live preview could materialize as a temporary streamed message plus a second final reply.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

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

  • Verified scenarios:
    • pnpm vitest run src/telegram/bot.helpers.test.ts src/telegram/bot-message.test.ts src/telegram/draft-stream.test.ts src/telegram/bot-message-dispatch.test.ts
    • pnpm vitest run src/telegram/bot-native-commands.test.ts src/telegram/bot-native-commands.group-auth.test.ts src/telegram/bot-native-commands.plugin-auth.test.ts src/telegram/bot-native-commands.session-meta.test.ts src/telegram/bot-native-commands.skills-allowlist.test.ts
    • confirmed processor-level scoped overrides for DM and topic/group paths via updated unit tests
    • confirmed quoted DM preview streaming path still stays on one reply message via existing draft-stream coverage in this branch
  • Edge cases checked:
    • topic override precedence over group/account defaults
    • legacy scoped streamMode still resolves correctly
    • native command replies follow scoped replyToMode
  • What you did not verify:
    • live manual Telegram traffic against the real Bot API from this source checkout
    • non-Telegram channels

Compatibility / Migration

  • Backward compatible? (Yes/No) Yes
  • Config/env changes? (Yes/No) No
  • Migration needed? (Yes/No) No
  • If yes, exact upgrade steps:

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly:
    • remove the scoped Telegram overrides and fall back to account-level channels.telegram.replyToMode / channels.telegram.streaming
    • revert commits 417217f8d2 and 45cba36cdf
  • Files/config to restore:
    • src/telegram/bot-message.ts
    • src/telegram/bot-native-commands.ts
    • src/telegram/bot/helpers.ts
    • src/config/types.telegram.ts
    • src/config/zod-schema.providers-core.ts
  • Known bad symptoms reviewers should watch for:
    • scoped DM/group/topic reply settings appear ignored
    • quoted Telegram streaming creates two messages or deletes the streamed preview after final delivery

Risks and Mitigations

List only real risks for this PR. Add/remove entries as needed. If none, write None.

  • Risk: scoped override precedence could diverge between normal replies and native command replies.
    • Mitigation: both paths now use the same helper-based reply mode resolution, with tests covering processor dispatch and native command delivery.
  • Risk: quoted DM streaming could regress back to preview/final double delivery when scoped overrides are enabled.
    • Mitigation: this branch includes the quoted DM single-message preview fix and keeps the dedicated draft-stream test coverage green.

Changed files

  • .secrets.baseline (modified, +11/-11)
  • docs/channels/telegram.md (modified, +17/-1)
  • docs/gateway/configuration-reference.md (modified, +11/-0)
  • src/cli/daemon-cli/lifecycle.test.ts (modified, +10/-11)
  • src/config/types.telegram.ts (modified, +18/-0)
  • src/config/zod-schema.providers-core.ts (modified, +9/-0)
  • src/secrets/runtime.test.ts (modified, +12/-2)
  • src/telegram/bot-message-dispatch.test.ts (modified, +72/-3)
  • src/telegram/bot-message.test.ts (modified, +49/-1)
  • src/telegram/bot-message.ts (modified, +25/-2)
  • src/telegram/bot-native-commands.ts (modified, +28/-5)
  • src/telegram/bot.helpers.test.ts (modified, +41/-1)
  • src/telegram/bot/helpers.ts (modified, +30/-0)

Code Example

const hasNativeQuoteReply = replyToMode !== "off" && Object.keys(replyQuoteByMessageId).length > 0;
const canStreamAnswerDraft = previewStreamingEnabled && !hasNativeQuoteReply && ...

---

// Before (bug)
const hasNativeQuoteReply = replyToMode !== "off" && Object.keys(replyQuoteByMessageId).length > 0;

// After
const hasNativeQuoteReply = replyToMode !== "off" && replyQuoteText != null && replyQuoteMessageId != null;

---

{
  "channels": {
    "telegram": {
      "replyToMode": "first",
      "streaming": {
        "mode": "partial"
      }
    }
  }
}
RAW_BUFFERClick to expand / collapse

Bug type

Regression (worked before, now fails)

Summary

When channels.telegram.replyToMode is set to "first" (or any non-"off" value), Telegram streaming preview (streaming.mode: "partial") is completely disabled. The bot produces no visible output during generation, then delivers all messages at once when the run completes.

Steps to reproduce

  1. Set channels.telegram.replyToMode to "first" (non-default) in openclaw.json
  2. Set channels.telegram.streaming.mode to "partial"
  3. Send a message to the bot and observe response delivery

Expected behavior

Streaming preview should work normally — text appears character by character (or in chunks) via editMessageText on the preview message. The replyToMode setting should only affect which message is replied to, not whether streaming is active.

Actual behavior

Zero visible output during generation. All response messages arrive at once after the run finishes. Streaming is completely non-functional.

Root cause

In extensions/telegram/src/bot-message-dispatch.ts (dist: bot-msflwCEW.js in 2026.4.26):

const hasNativeQuoteReply = replyToMode !== "off" && Object.keys(replyQuoteByMessageId).length > 0;
const canStreamAnswerDraft = previewStreamingEnabled && !hasNativeQuoteReply && ...

When replyToMode !== "off", the handler always calls addTelegramNativeQuoteCandidate(replyQuoteByMessageId, msg.message_id, ...), which unconditionally populates replyQuoteByMessageId. This makes Object.keys(replyQuoteByMessageId).length > 0 always true, so hasNativeQuoteReply is always true, and canStreamAnswerDraft is always false.

The intended reason to disable streaming is when the reply contains a Telegram quote (i.e. reply_parameters.quote), because that parameter cannot be passed via editMessageText. A plain reply_to_message_id is fully compatible with streaming — it is passed on the first sendMessage call and subsequent editMessageText calls work fine. The code already correctly passes draftReplyToMessageId into createTelegramDraftStream, confirming the two features are designed to coexist.

Suggested fix

// Before (bug)
const hasNativeQuoteReply = replyToMode !== "off" && Object.keys(replyQuoteByMessageId).length > 0;

// After
const hasNativeQuoteReply = replyToMode !== "off" && replyQuoteText != null && replyQuoteMessageId != null;

This limits the streaming-disable guard to only genuine Telegram quote replies (where the user has selected quoted text), leaving plain reply-to-message streaming fully functional.

Configuration

{
  "channels": {
    "telegram": {
      "replyToMode": "first",
      "streaming": {
        "mode": "partial"
      }
    }
  }
}

Version

OpenClaw 2026.4.26

extent analysis

TL;DR

Update the hasNativeQuoteReply condition to only disable streaming when a genuine Telegram quote reply is present.

Guidance

  • Review the bot-message-dispatch.ts file and update the hasNativeQuoteReply condition as suggested in the issue to fix the streaming disable guard.
  • Verify that the replyToMode setting only affects which message is replied to, not whether streaming is active, by testing with different replyToMode values.
  • Test the updated code with the provided configuration to ensure streaming preview works as expected.
  • Confirm that plain reply-to-message streaming is fully functional with the updated code.

Example

const hasNativeQuoteReply = replyToMode !== "off" && replyQuoteText != null && replyQuoteMessageId != null;

Notes

This fix assumes that the replyQuoteText and replyQuoteMessageId variables are correctly set when a genuine Telegram quote reply is present. If these variables are not set correctly, the fix may not work as expected.

Recommendation

Apply the suggested workaround by updating the hasNativeQuoteReply condition to fix the streaming disable guard, as this is a targeted fix that addresses the root cause of the issue.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

Streaming preview should work normally — text appears character by character (or in chunks) via editMessageText on the preview message. The replyToMode setting should only affect which message is replied to, not whether streaming is active.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING