Skip to content

fix(telegram): skip duplicate final emit when preview already covers chunks (#87624)#87686

Open
sweetcornna wants to merge 1 commit into
openclaw:mainfrom
sweetcornna:fix/87624-telegram-streaming-duplicate
Open

fix(telegram): skip duplicate final emit when preview already covers chunks (#87624)#87686
sweetcornna wants to merge 1 commit into
openclaw:mainfrom
sweetcornna:fix/87624-telegram-streaming-duplicate

Conversation

@sweetcornna

Copy link
Copy Markdown
Contributor

Summary

What problem does this PR solve?

  • Long Telegram replies (>4096 chars) duplicate the entire preview content as a final emit pass in partial and block streaming modes. The user sees the same reply rendered twice.

Why does this matter now?

  • Labelled regression / P1 / impact:message-loss. The bug deterministically corrupts every long Telegram reply in the documented-default streaming.mode: "partial" and in "block"; only "progress" is clean.

What is the intended outcome?

  • The lane finalizer treats the preview as covering its delivered prefix even when that prefix ends below the lane chunker's first-chunk boundary, so retained overflow preview pages stay visible and only the missing suffix is emitted.

What is intentionally out of scope?

  • No change to draft-stream overflow chaining behavior, dispatcher-side chunking, or progress mode.

What does success look like?

  • partial mode: a >4096-char reply lands in ceil(reply_length / textChunkLimit) Telegram messages instead of roughly twice that.
  • block mode: the first chunk shown as a preview is reused as final chunk 0 instead of being re-sent alongside chunks 1..N.
  • progress mode: unchanged.

What should reviewers focus on?

  • extensions/telegram/src/lane-delivery-text-deliverer.ts:405: the relaxed prefix-detection check now also covers deliveredStreamTextAfterStop.length <= firstChunk.length as long as the delivered text is a real prefix of finalText.

Linked context

Which issue does this close?

Closes #87624

Which issues, PRs, or discussions are related?

Related #84885 (adjacent retained-preview cluster, per ClawSweeper review on #87624).

Was this requested by a maintainer or owner?

ClawSweeper review on #87624 marked it clawsweeper:source-repro + clawsweeper:queueable-fix + clawsweeper:fix-shape-clear and pointed at this exact handoff path.

Real behavior proof (required for external PRs)

  • Behavior addressed: extensions/telegram/src/lane-delivery-text-deliverer.ts:405 rejected the after-stop prefix unless deliveredText.length > firstChunk.length, so when the draft preview ended on a tighter boundary (HTML rendering caps the preview, or stop() failed before the firstChunk update lands) the finalizer fell through to params.sendPayload(applyTextToPayload(payload, text)) — sending the full final text again — while the preview / retained overflow pages stayed visible.
  • Real environment tested: macOS Darwin 25.5.0, Node 22, pnpm 11.2.2; this PR ran the targeted vitest suites locally inside the repo worktree. Live Telegram polling was not exercised against a real bot account in this run.
  • Exact steps or command run after this patch:
    • pnpm install --frozen-lockfile
    • node scripts/run-vitest.mjs extensions/telegram/src/lane-delivery.test.ts extensions/telegram/src/draft-stream.test.ts extensions/telegram/src/bot-message-dispatch.test.ts
    • node scripts/run-vitest.mjs extensions/telegram (full plugin suite)
    • Confirmed regression by temporarily restoring the length > firstChunk guard: the new test hands off from a shorter streamed prefix when stop ends below the first chunk boundary failed with expected 'sent' to be 'preview-finalized' (lane fell through to sendPayload(finalText) and clearDraftLane).
  • Evidence after fix (terminal capture):
    RUN  v4.1.7 /Users/cornna/project/openclaw_work/wt-87624
    Test Files  123 passed (123)
         Tests  1919 passed (1919)
    
  • Observed result after fix: With the relaxed check, the new tests pass and existing 1919 telegram tests stay green. The lane finalizer routes through finalizeDeliveredPrefix(deliveredStreamTextAfterStop, messageId) for any prefix snapshot, so the preview / retained pages remain the active delivery and only the missing suffix is sent via params.sendPayload(followUpPayload(...)).
  • What was not tested: Live Telegram Bot API polling against a real bot with streaming.mode: "partial" + textChunkLimit: 3800 and a >6000-char prompt. The fix path is exercised by unit tests but a live roundtrip is recommended before landing — cited under clawsweeper:source-repro.
  • Proof limitations or environment constraints: This worktree is a non-maintainer fork checkout; no Crabbox / Testbox access from here. The proof above is source-level + vitest, in line with the clawsweeper:source-repro label on [Bug]: Telegram streaming.mode: "partial" and "block" duplicate the full preview when reply >4096 chars #87624.

Tests and validation

Which commands did you run?

  • pnpm install --frozen-lockfile
  • node scripts/run-vitest.mjs extensions/telegram/src/lane-delivery.test.ts extensions/telegram/src/draft-stream.test.ts extensions/telegram/src/bot-message-dispatch.test.ts → 161 tests passed
  • node scripts/run-vitest.mjs extensions/telegram → 1919 tests passed
  • pnpm format:check extensions/telegram/src/lane-delivery-text-deliverer.ts extensions/telegram/src/lane-delivery.test.ts → clean
  • node scripts/run-oxlint.mjs extensions/telegram/src/lane-delivery-text-deliverer.ts extensions/telegram/src/lane-delivery.test.ts → exit 0
  • pnpm tsgo --noEmit -p extensions/telegram/tsconfig.json → clean

What regression coverage was added or updated?

  • extensions/telegram/src/lane-delivery.test.ts: does not duplicate the first chunk when block mode finalizes a long reply covers the block-mode variant (preview shows first chunk, final must reuse it for chunk 0 and only emit chunks 1..N).
  • extensions/telegram/src/lane-delivery.test.ts: hands off from a shorter streamed prefix when stop ends below the first chunk boundary covers the partial-mode variant where the preview's lastDeliveredText is a real prefix shorter than firstChunk — the failure mode that caused the dispatcher to fall through to sendPayload(finalText) and duplicate retained pages.

What failed before this fix, if known?

  • With the prior length > firstChunk guard restored, the new hands off from a shorter streamed prefix... test failed at expectPreviewFinalized with expected 'sent' to be 'preview-finalized' — i.e., the lane fell through to fullText delivery instead of finalizing in place.

If no test was added, why not?

  • N/A. Added regression tests for both partial and block surface shapes called out in the issue.

Risk checklist

Did user-visible behavior change? (Yes)

  • Telegram message counts for long streamed replies in partial/block mode change from "preview + full duplicated final pass" to "preview + suffix only". This is the bug fix and matches the documented behavior already shipped for progress mode.

Did config, environment, or migration behavior change? (No)

  • No config keys touched; no doctor migration.

Did security, auth, secrets, network, or tool execution behavior change? (No)

What is the highest-risk area?

  • The relaxed prefix check now triggers finalizeDeliveredPrefix even when the delivered preview is shorter than firstChunk. If stream.lastDeliveredText ever returned a non-prefix prefix-like string (e.g., the stream had been reset to fresh state with stale content), we already bail out via the isDeliveredPrefix guard. The new branch only fires when finalText.startsWith(deliveredText) is true, so it cannot leak unrelated content.

How is that risk mitigated?

  • Two regression tests, plus the existing 32 lane-delivery tests covering longer-prefix, equal-firstChunk, no-prefix, no-stream, oversized buttons, voice-fallback, and reasoning-lane shapes all stay green.

Current review state

What is the next action?

  • Maintainer review and (if required) live Telegram proof per clawsweeper:source-repro before landing.

What is still waiting on author, maintainer, CI, or external proof?

  • Live Telegram roundtrip from a maintainer environment to confirm the visible message-count change matches the unit-test trace.

Which bot or reviewer comments were addressed?

openclaw#87624)

In partial/block streaming.mode the draft preview can end at a tighter
boundary than the lane chunker (e.g. HTML rendering caps the preview a
few characters short of the first chunk's raw-text boundary, or stop()
failed before the firstChunk update lands). The finalizer rejected those
shorter-but-still-prefix snapshots and fell through to
sendPayload(finalText), which re-chunked the full reply via
deliverReplies. Any retained overflow preview pages stayed visible while
the final pass spawned fresh duplicates of each chunk, so a >4096-char
reply landed twice on Telegram.

Generalize the after-stop prefix check: if the delivered stream text is
any prefix of the final text, hand off from that boundary so we only
emit the missing suffix. Drop the previous "must exceed firstChunk"
guard that only covered multi-preview chains.
@openclaw-barnacle openclaw-barnacle Bot added channel: telegram Channel integration: telegram size: S triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. labels May 28, 2026
@clawsweeper

clawsweeper Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed May 29, 2026, 1:09 AM ET / 05:09 UTC.

Summary
Review failed before ClawSweeper could summarize the requested change.

PR surface: Source +3, Tests +64. Total +67 across 2 files.

Reproducibility: unclear. The review failed before ClawSweeper could establish a reproduction path.

Review metrics: none identified.

Merge readiness
Overall: 🌊 off-meta tidepool
Proof: 🌊 off-meta tidepool
Patch quality: 🌊 off-meta tidepool
Result: rating does not apply to this item.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Risk before merge

  • [P1] No close action taken because the review did not complete.

Maintainer options:

  1. Decide the mitigation before merge
    Retry the Codex review after fixing the execution failure.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P1] Review did not complete, so no work-lane recommendation was made.
Review details

Best possible solution:

Retry the Codex review after fixing the execution failure.

Do we have a high-confidence way to reproduce the issue?

Unclear. The review failed before ClawSweeper could establish a reproduction path.

Is this the best way to solve the issue?

Unclear. Retry the review first so ClawSweeper can evaluate the actual issue and fix direction.

AGENTS.md: unclear because the file could not be read completely.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 0e86ca135225.

Label changes

Label changes:

  • add rating: 🌊 off-meta tidepool: Overall readiness is 🌊 off-meta tidepool; proof is 🌊 off-meta tidepool and patch quality is 🌊 off-meta tidepool.
  • remove mantis: telegram-visible-proof: Current Telegram visible-proof status is not_needed.
  • remove P1: Current review triage priority is none.
  • remove rating: 🦪 silver shellfish: Current PR rating is rating: 🌊 off-meta tidepool, so this older rating label is no longer current.
  • remove merge-risk: 🚨 message-delivery: Current PR review selected no merge-risk labels.
  • remove status: 📣 needs proof: Current PR status no longer selects a status label.

Label justifications:

  • rating: 🌊 off-meta tidepool: Overall readiness is 🌊 off-meta tidepool; proof is 🌊 off-meta tidepool and patch quality is 🌊 off-meta tidepool.
Evidence reviewed

PR surface:

Source +3, Tests +64. Total +67 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 7 4 +3
Tests 1 64 0 +64
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 71 4 +67

What I checked:

  • failure reason: codex execution failed.
  • codex failure detail: Codex review failed for this PR with exit 1.
  • codex stdout: Per-item Codex failure; continuing with the rest of the shard.

Likely related people:

  • unknown: Codex failed before it could trace repository history. (role: review did not complete; confidence: low)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. mantis: telegram-visible-proof Mantis should capture Telegram visible proof. P1 High-priority user-facing bug, regression, or broken workflow. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. labels May 28, 2026
@clawsweeper clawsweeper Bot added rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. mantis: telegram-visible-proof Mantis should capture Telegram visible proof. labels May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: telegram Channel integration: telegram merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. P1 High-priority user-facing bug, regression, or broken workflow. rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. size: S triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Telegram streaming.mode: "partial" and "block" duplicate the full preview when reply >4096 chars

2 participants