openclaw - ✅(Solved) Fix Telegram: streaming.mode 'partial' causes intermediate message deletion (visible 'ghost message' UX bug) [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#72706Fetched 2026-04-28 06:33:12
View on GitHub
Comments
1
Participants
2
Timeline
5
Reactions
0
Author
Timeline (top)
cross-referenced ×2referenced ×2commented ×1

Fix Action

Fix / Workaround

Workaround validated by both stacks

Both verified the bug is present and the workaround (mode: "final") eliminates it.

PR fix notes

PR #1: fix(telegram): default streaming mode to 'final' to prevent ghost message UX bug

Description (problem / solution / changelog)

[Bug Fix] Telegram streaming mode default to off — resolve ghost message UX bug (#72706)


1. Context

Why does this PR exist? What issue is being solved?

When streaming.mode is not explicitly set, Telegram preview defaulted to partial mode. This causes a visible "ghost message" UX bug where an intermediate streaming response briefly appears then gets deleted before the final response arrives.

Issue: https://github.com/openclaw/openclaw/issues/72706


2. Solution

preview-streaming.ts:

  • Changed default value of resolveTelegramPreviewStreamMode from "partial" to "off"
  • Added "final" to valid TelegramPreviewStreamMode type union
sequenceDiagram
  participant User as Telegram User
  participant Bot as OpenClaw Bot
  participant Stream as Stream Handler

  Note over Bot: streaming.mode = undefined
  Bot->>Stream: resolveTelegramPreviewStreamMode()
  Stream-->>Bot: return "off" (new default)
  Note over Bot: off mode: no preview, final message only
  Bot->>User: Final message only (no ghost)

Effect: off mode disables preview streaming — no intermediate message to delete.


3. Impact

  • Breaking changes: No — only default behavior changed, users can still set partial or block explicitly
  • DB migration: No
  • Performance: No impact
  • Side effects: Users who relied on default partial preview will now get no preview (cleaner UX)

4. Testing

  • Build pass
  • TypeScript check pass

Changed files

  • .agents/skills/openclaw-testing/SKILL.md (modified, +46/-12)
  • .github/workflows/full-release-validation.yml (modified, +87/-43)
  • .github/workflows/npm-telegram-beta-e2e.yml (modified, +11/-1)
  • .github/workflows/openclaw-live-and-e2e-checks-reusable.yml (modified, +212/-32)
  • .github/workflows/openclaw-release-checks.yml (modified, +10/-4)
  • .github/workflows/package-acceptance.yml (modified, +1/-0)
  • .github/workflows/qa-live-transports-convex.yml (modified, +16/-6)
  • AGENTS.md (modified, +1/-2)
  • CHANGELOG.md (modified, +85/-25)
  • Dockerfile (modified, +7/-1)
  • apps/macos/Sources/OpenClawProtocol/GatewayModels.swift (modified, +16/-2)
  • apps/macos/Tests/OpenClawIPCTests/GatewayWebSocketTestSupport.swift (modified, +1/-0)
  • apps/macos/Tests/OpenClawIPCTests/MacGatewayChatTransportMappingTests.swift (modified, +1/-1)
  • apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayChannel.swift (modified, +2/-1)
  • apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift (modified, +16/-2)
  • docs/.generated/config-baseline.sha256 (modified, +3/-3)
  • docs/.generated/plugin-sdk-api-baseline.sha256 (modified, +2/-2)
  • docs/automation/hooks.md (modified, +19/-15)
  • docs/automation/taskflow.md (modified, +3/-0)
  • docs/automation/tasks.md (modified, +2/-0)
  • docs/channels/discord.md (modified, +1/-0)
  • docs/channels/pairing.md (modified, +1/-1)
  • docs/ci.md (modified, +21/-7)
  • docs/cli/agent.md (modified, +3/-0)
  • docs/cli/doctor.md (modified, +2/-0)
  • docs/cli/mcp.md (modified, +6/-2)
  • docs/cli/memory.md (modified, +1/-1)
  • docs/cli/nodes.md (modified, +2/-0)
  • docs/cli/plugins.md (modified, +1/-1)
  • docs/concepts/compaction.md (modified, +7/-0)
  • docs/concepts/memory-builtin.md (modified, +2/-0)
  • docs/concepts/memory-qmd.md (modified, +10/-3)
  • docs/concepts/model-failover.md (modified, +3/-1)
  • docs/concepts/model-providers.md (modified, +1/-1)
  • docs/docs.json (modified, +8/-0)
  • docs/gateway/bonjour.md (modified, +6/-0)
  • docs/gateway/config-agents.md (modified, +1/-1)
  • docs/gateway/config-tools.md (modified, +3/-3)
  • docs/gateway/configuration-reference.md (modified, +4/-1)
  • docs/gateway/doctor.md (modified, +5/-2)
  • docs/gateway/local-models.md (modified, +32/-2)
  • docs/gateway/pairing.md (modified, +2/-0)
  • docs/gateway/protocol.md (modified, +10/-7)
  • docs/gateway/troubleshooting.md (modified, +3/-0)
  • docs/help/testing.md (modified, +2/-2)
  • docs/nodes/audio.md (modified, +1/-0)
  • docs/nodes/index.md (modified, +3/-1)
  • docs/plugins/architecture-internals.md (modified, +10/-0)
  • docs/plugins/architecture.md (modified, +4/-0)
  • docs/plugins/building-plugins.md (modified, +1/-0)
  • docs/plugins/bundles.md (modified, +1/-0)
  • docs/plugins/compatibility.md (modified, +2/-1)
  • docs/plugins/google-meet.md (modified, +26/-18)
  • docs/plugins/sdk-migration.md (modified, +41/-0)
  • docs/plugins/sdk-runtime.md (modified, +51/-4)
  • docs/plugins/sdk-subpaths.md (modified, +10/-7)
  • docs/plugins/sdk-testing.md (modified, +11/-2)
  • docs/providers/google.md (modified, +9/-3)
  • docs/providers/groq.md (modified, +8/-0)
  • docs/providers/lmstudio.md (modified, +10/-2)
  • docs/providers/ollama.md (modified, +36/-1)
  • docs/providers/openai.md (modified, +11/-1)
  • docs/providers/openrouter.md (modified, +7/-7)
  • docs/providers/qwen.md (modified, +15/-2)
  • docs/providers/vllm.md (modified, +22/-1)
  • docs/reference/RELEASING.md (modified, +25/-18)
  • docs/reference/memory-config.md (modified, +2/-0)
  • docs/reference/session-management-compaction.md (modified, +3/-0)
  • docs/reference/test.md (modified, +2/-1)
  • docs/tools/loop-detection.md (modified, +3/-0)
  • docs/tools/plugin.md (modified, +12/-1)
  • docs/tools/thinking.md (modified, +5/-0)
  • docs/web/control-ui.md (modified, +5/-3)
  • extensions/acpx/package.json (modified, +1/-0)
  • extensions/acpx/src/claude-agent-acp-completion.test.ts (added, +129/-0)
  • extensions/active-memory/index.test.ts (modified, +7/-1)
  • extensions/active-memory/index.ts (modified, +12/-4)
  • extensions/anthropic/register.runtime.ts (modified, +4/-38)
  • extensions/anthropic/stream-wrappers.test.ts (modified, +50/-0)
  • extensions/anthropic/stream-wrappers.ts (modified, +62/-1)
  • extensions/bluebubbles/src/actions.test.ts (modified, +17/-4)
  • extensions/bluebubbles/src/attachments.test.ts (modified, +1/-1)
  • extensions/bluebubbles/src/monitor.ts (modified, +1/-1)
  • extensions/bluebubbles/src/test-support/monitor-test-support.ts (modified, +2/-1)
  • extensions/bonjour/src/advertiser.test.ts (modified, +47/-1)
  • extensions/bonjour/src/advertiser.ts (modified, +25/-1)
  • extensions/browser/src/browser-tool.actions.ts (modified, +8/-8)
  • extensions/browser/src/browser-tool.runtime.ts (modified, +1/-1)
  • extensions/browser/src/browser-tool.test.ts (modified, +4/-3)
  • extensions/browser/src/browser-tool.ts (modified, +11/-9)
  • extensions/browser/src/browser/browser-utils.test.ts (modified, +1/-1)
  • extensions/browser/src/browser/client-fetch.attach-only.e2e.test.ts (modified, +14/-2)
  • extensions/browser/src/browser/client-fetch.loopback-auth.test.ts (modified, +1/-0)
  • extensions/browser/src/browser/client-fetch.ts (modified, +5/-5)
  • extensions/browser/src/browser/config-refresh-source.ts (modified, +2/-2)
  • extensions/browser/src/browser/control-auth.auto-token.test.ts (modified, +15/-12)
  • extensions/browser/src/browser/control-auth.ts (modified, +12/-6)
  • extensions/browser/src/browser/control-service.plugin-disabled.test.ts (modified, +1/-0)
  • extensions/browser/src/browser/profiles-service.test.ts (modified, +21/-13)
  • extensions/browser/src/browser/profiles-service.ts (modified, +11/-5)

PR #72759: fix(telegram): default streaming mode to final to prevent ghost message UX bug

Description (problem / solution / changelog)

[Bug Fix] Telegram streaming mode default to final — resolve ghost message UX bug (#72706)


1. Context

Why does this PR exist? What issue is being solved?

When streaming.mode is not explicitly set, Telegram preview defaulted to partial mode. This causes a visible "ghost message" UX bug where an intermediate streaming response briefly appears then gets deleted before the final response arrives.

Issue: https://github.com/openclaw/openclaw/issues/72706


2. Solution

preview-streaming.ts:

  • Changed default value of resolveTelegramPreviewStreamMode from "partial" to "final"
sequenceDiagram
  participant User as Telegram User
  participant Bot as OpenClaw Bot
  participant Stream as Stream Handler

  Note over Bot: streaming.mode = undefined
  Bot->>Stream: resolveTelegramPreviewStreamMode()
  Stream-->>Bot: return "final" (new default)
  Note over Bot: final mode: only emit when complete
  Bot->>User: Final message only (no ghost)

Effect: final mode only emits output when complete — no intermediate message to delete.


3. Impact

  • Breaking changes: No — only default behavior changed, users can still set partial explicitly
  • DB migration: No
  • Performance: No impact
  • Side effects: Users who already configured streaming.mode: "partial" will see different behavior — docs update recommended

4. Testing

  • Build pass
  • Unit test pass (draft-stream, lane-delivery)

Changed files

  • extensions/telegram/src/preview-streaming.ts (modified, +1/-1)

Code Example

User: "lánzame estrategia ads TikTok CKS Madrid 30 días"
Bot:  starts a TL;DR + question reply (visible streaming)
User: "sí, comparte los puntos clave"
Bot:  shows "escribiendo..." indicator
Bot:  starts redacting detailed plan
Bot:  DELETES that intermediate message
Bot:  delivers complete final response with all key points (success)

---

"messengers": {
  "telegram": {
    "dmPolicy": "allowlist",
    "groupPolicy": "allowlist",
    "streaming": {
      "mode": "partial"   // ← suspect setting
    },
    "botToken": "<REDACTED>",
    "enabled": true,
    "allowFrom": ["<REDACTED>"]
  }
}

---

"streaming": {
  "mode": "final"
}
RAW_BUFFERClick to expand / collapse

Bug: messengers.telegram.streaming.mode: "partial" causes Telegram message-id replacement (visible "deleted" effect)

Affected versions: [email protected] (David Utrero, VPS Hostinger) + [email protected] post-Hermes update (Sergio Barrera, dedicated server)

Symptom

When messengers.telegram.streaming.mode is set to "partial" in openclaw.json, the agent's Telegram bot exhibits a "ghost message" UX bug:

  1. The bot starts to send an intermediate streaming response (chunks visible to the user).
  2. The bot then deletes that intermediate response before delivering the final.
  3. The final response is correctly delivered (intact, complete).

The user sees a "phantom message" appear briefly (1-2s) and then disappear before the real answer arrives. Functional output is unaffected — only the intermediate UX is broken.

Empirical evidence — David's stack ([email protected])

Detected during empirical verification of a new agent (cks-architect) on Telegram bot @cks_system_bot (single user allowlist):

User: "lánzame estrategia ads TikTok CKS Madrid 30 días"
Bot:  starts a TL;DR + question reply (visible streaming)
User: "sí, comparte los puntos clave"
Bot:  shows "escribiendo..." indicator
Bot:  starts redacting detailed plan
Bot:  DELETES that intermediate message
Bot:  delivers complete final response with all key points (success)

Configuration excerpt (openclaw.json, secrets redacted):

"messengers": {
  "telegram": {
    "dmPolicy": "allowlist",
    "groupPolicy": "allowlist",
    "streaming": {
      "mode": "partial"   // ← suspect setting
    },
    "botToken": "<REDACTED>",
    "enabled": true,
    "allowFrom": ["<REDACTED>"]
  }
}

Empirical evidence — Sergio's stack ([email protected] post-Hermes update)

Independent reproduction by @sergio-barrera (Lobster CKS stack) on a different OpenClaw deployment (Hermes updated NPM from 2026.4.222026.4.24 automatically). His finding:

Verificación empírica HOY post-update Hermes: confirmo bridge-david.streaming.mode SIGUE en "partial". Hermes actualizó NPM a 2026.4.24 + reparó OAuth + reparó Telegram, pero NO tocó este campo. C20 sigue abierto en mi lado, mismo síntoma que tu GAP-F55.

— same symptom (intermediate message deleted before final), confirmed cross-stack.

Hypothesised mechanism

In partial mode, the bot emits intermediate chunks via stream WebSocket. When the model decides to rewrite the complete final response (rather than extend the intermediate), the Telegram client interprets it as editMessageText with a new message_id, which renders as "deletion of previous + insertion of new" instead of in-place extension.

In final mode, only the final output is emitted, so the "deleted" intermediate is never visible.

Workaround validated by both stacks

Switch to messengers.telegram.streaming.mode: "final" in openclaw.json:

"streaming": {
  "mode": "final"
}

After hot-reload, the ghost-message effect disappears. Functional output is identical.

Suggested fix direction

Either:

  1. Default to final mode for Telegram messenger (the partial mode UX is bug-prone for users seeing rewrites), or
  2. Use editMessageText with the same message_id in partial mode when the model rewrites — extend the existing message instead of replacing it, or
  3. Document the asymmetry explicitly in the SDK docs so users know partial mode triggers visible rewrites in Telegram clients.

Cross-verification

This issue was reproduced empirically in 2 independent OpenClaw deployments × 2 different package versions:

  • David Utrero (@David-CKS) — [email protected] on VPS Hostinger, single-user Telegram bot.
  • Sergio Barrera (@sergio-barrera) — [email protected] post-Hermes auto-update on dedicated server.

Both verified the bug is present and the workaround (mode: "final") eliminates it.

Related

  • Internal tracking ID (David): GAP-F55 in our config/gaps.yaml
  • Internal tracking ID (Sergio): C20 in his stack notes

cc: @sergio-barrera (will leave co-author confirmation as a comment)

extent analysis

TL;DR

Switching to messengers.telegram.streaming.mode: "final" in openclaw.json resolves the "ghost message" issue.

Guidance

  • The issue is caused by the partial streaming mode, which emits intermediate chunks and then rewrites the final response, leading to a "deleted" effect in Telegram clients.
  • To verify the issue, check if the "ghost message" effect disappears after switching to final mode.
  • To mitigate the issue, update the openclaw.json configuration file with the suggested change.
  • Consider documenting the asymmetry between partial and final modes in the SDK docs to inform users of the potential UX implications.

Example

"streaming": {
  "mode": "final"
}

Notes

The suggested fix direction includes defaulting to final mode, using editMessageText with the same message_id in partial mode, or documenting the asymmetry explicitly in the SDK docs.

Recommendation

Apply the workaround by switching to messengers.telegram.streaming.mode: "final" in openclaw.json, as it has been validated by both affected stacks and eliminates the "ghost message" effect.

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…

Still need to ship something?

×6

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

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix Telegram: streaming.mode 'partial' causes intermediate message deletion (visible 'ghost message' UX bug) [2 pull requests, 1 comments, 2 participants]