Skip to content

fix(gateway): stop payload-less split delivery from swallowing Telegram finals - #78558

Closed
HexLab98 wants to merge 2 commits into
NousResearch:mainfrom
HexLab98:fix/78541-split-delivery-suppression-swallow
Closed

fix(gateway): stop payload-less split delivery from swallowing Telegram finals#78558
HexLab98 wants to merge 2 commits into
NousResearch:mainfrom
HexLab98:fix/78541-split-delivery-suppression-swallow

Conversation

@HexLab98

@HexLab98 HexLab98 commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Multi-message stream delivery was setting final_content_delivered without recording a payload, so delivered_final_matches() returned None and the gateway's legacy trust suppressed the normal final send.
  • On Telegram group/forum sessions that left users with a complete response in the session DB and nothing in chat (#78541).
  • Keep an unsplit _stream_ledger for overflow splits, record it on turn-final delivery, and treat payload-less split delivery as a mismatch (False) so the complete reply is reconciled or sent.

Test plan

  • scripts/run_tests.sh tests/gateway/test_stale_finalize_suppression.py -q
  • scripts/run_tests.sh tests/gateway/test_stale_finalize_suppression.py tests/gateway/test_stream_consumer_fresh_final.py tests/gateway/test_telegram_final_delivery.py -q
  • Reproduce on a Telegram forum-topic group with streaming enabled: interrupt/partial flush mid-turn, then confirm the next complete reply is delivered (no lone Suppressing normal final send ghost).

Fixes #78541

Record an unsplit stream ledger for multi-message deliveries and refuse
legacy trust when split delivery left no payload, so Telegram group
sessions no longer suppress a complete reply after an early/partial
finalize (NousResearch#78541).
Add unit and GatewayRunner boundary coverage for the NousResearch#78541 shape where
final_content_delivered is set via split delivery with no recorded
payload.
@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery platform/telegram Telegram bot adapter P1 High — major feature broken, no workaround sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Aug 4, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related: #78556 is an open competing fix for #78541. Both address payload-less split delivery, but #78558 uses an unsplit stream ledger while #78556 records joined split payloads; maintainer selection is needed.

@kshitijk4poor

Copy link
Copy Markdown
Collaborator

Merged via #79669 — your commits were cherry-picked with authorship preserved (c46027b04, a2ca5c2a7 on main).

Your diagnosis was exactly right: the multi-message split path set final_content_delivered=True without recording a payload, so delivered_final_matches() returned None and gateway/run.py's legacy trust suppressed the real reply. I reproduced the swallow on main with a probe driving the real run() loop and confirmed your fix clears it. The _stream_ledger approach is also the right mechanism — I checked whether existing state could cover it and it can't: _delivered_segment_texts is only appended in _reset_segment_state (which the split paths never reach — they clear _last_sent_text first), and has_delivered_text compares whole-string equality per entry, so it structurally cannot match chunk1 + chunk2.

I added follow-up commits before merging, because six code paths set _turn_split_delivery and the ledger was wired into one of them. The other five inherited the new "payload-less split ⇒ mismatch" verdict without being able to satisfy it, which traded the swallow for the opposite defect:

shape before your PR alone merged
complete overflow split, all delivered suppress ✅ suppress ✅ suppress ✅
split + flood-controlled final edit suppress ✅ duplicate ❌ suppress ✅
split + fallback final send (tail-only record) suppress ✅ duplicate ❌ suppress ✅
split on Telegram's fresh-final route swallow ❌ swallow ❌ delivered ✅

Row 2 would have re-opened the double-answer that #45517 fixed (#36965 / #25349). Row 4 is the one worth flagging: _try_fresh_final is Telegram's default finalize route, and it deletes every tracked preview — including the sealed head chunks — while sending only the tail, so the reported symptom would likely have persisted on the exact platform the issue is about.

The follow-up fixes the producers rather than only distrusting them at the boundary: record the visible payload on the failed-final-edit branch, route the three fallback sites through _record_turn_final_payload instead of tail-only direct assignment, refuse fresh-final once a head is sealed, set the split flag at seal time so the tail's own finalize sees it, and skip run.py's in-place reconcile edit on splits (it targets only the last chunk, so editing it with the complete response repeated every head's text). Also dropped the or "".join(chunks) fallback — the ledger can't be empty at that call site, and joined chunks carry injected fence markers that could never equal final_response.

Added four tests that drive the real overflow-split loop instead of hand-setting private flags, each mutation-checked so reverting any individual fix turns its own test red. Full CI green (8/8 slices).

Thanks — the root-cause analysis and the ledger design were the hard part, and both shipped.

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

Labels

comp/gateway Gateway runner, session dispatch, delivery P1 High — major feature broken, no workaround platform/telegram Telegram bot adapter sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Suppressing normal final send swallows complete replies on Telegram group/forum sessions (payload-less split-delivery flags final_content_delivered)

3 participants