Skip to content

fix(gateway): flush fallback text before segment reset to prevent silent data loss#8417

Closed
lawrence3699 wants to merge 1 commit into
NousResearch:mainfrom
lawrence3699:fix/stream-fallback-text-loss-on-tool-boundary
Closed

fix(gateway): flush fallback text before segment reset to prevent silent data loss#8417
lawrence3699 wants to merge 1 commit into
NousResearch:mainfrom
lawrence3699:fix/stream-fallback-text-loss-on-tool-boundary

Conversation

@lawrence3699

Copy link
Copy Markdown

What does this PR do?

Fixes #8124 — streaming text silently dropped when a tool boundary arrives during fallback mode.

Problem

When a progressive edit fails (flood control), the stream consumer enters fallback mode: it sets _fallback_final_send = True and continues accumulating text. At _DONE, the accumulated text is sent as a new message via _send_fallback_final().

However, when a tool boundary (_NEW_SEGMENT) arrives before _DONE, _reset_segment_state() clears _accumulated, _fallback_final_send, and _fallback_prefix — destroying the pending content. The user never receives the text that was buffered between the edit failure and the tool call.

Before: ["Hello ▉", "Next segment"] — " world" silently dropped
After: ["Hello ▉", "world", "Next segment"] — all text delivered

Fix

Flush pending fallback text via _send_fallback_final() before calling _reset_segment_state() on segment breaks. This mirrors the existing flush pattern in the _DONE handler (line 241–242).

The flush is skipped for __no_edit__ platforms (Signal, webhook with github_comment) where all text intentionally accumulates across boundaries and is sent once at stream end.

Changes

  • gateway/stream_consumer.py: 3-line guard + flush before segment reset
  • tests/gateway/test_stream_consumer.py: updated existing test to verify content is delivered instead of dropped

How to Test

pytest tests/gateway/test_stream_consumer.py -v

All 28 tests pass, including the updated regression test and the existing __no_edit__ path test (test_no_message_id_segment_breaks_do_not_resend).

…ent data loss

When streaming edits fail (flood control) and a tool boundary arrives,
_reset_segment_state() clears the accumulated text without flushing it.
This silently drops content the user never received.

Flush pending fallback text before the reset, mirroring the existing
_DONE handler pattern. Skip the flush for __no_edit__ platforms (Signal,
webhook) where all text intentionally accumulates until stream end.

Fixes NousResearch#8124
Copilot AI review requested due to automatic review settings April 12, 2026 14:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a gateway streaming edge case where buffered text in “fallback mode” could be silently dropped when a tool boundary (_NEW_SEGMENT) arrives before _DONE, by flushing pending fallback text prior to resetting segment state.

Changes:

  • Flush pending fallback text on segment breaks (except __no_edit__ platforms) before calling _reset_segment_state().
  • Update the existing segment-break test to assert the buffered fallback tail is delivered rather than dropped.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
gateway/stream_consumer.py Adds a guard to flush accumulated fallback text before segment reset to prevent mid-stream data loss on tool boundaries.
tests/gateway/test_stream_consumer.py Updates regression coverage to verify fallback text is delivered across a tool boundary.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +272 to +276
# Flush any pending fallback text before resetting.
# Without this, accumulated text from a failed edit is
# silently dropped when a tool boundary arrives. (#8124)
# Skip for __no_edit__ platforms (Signal, webhook) where
# all text intentionally accumulates until _DONE.
Comment on lines +277 to 280
if (self._fallback_final_send and self._accumulated
and self._message_id != "__no_edit__"):
await self._send_fallback_final(self._accumulated)
self._reset_segment_state(preserve_no_edit=True)
@teknium1

Copy link
Copy Markdown
Contributor

Closing as superseded by #12414 (merged), which adopted @konsisumer's PR #11974 for the same bug.

Your PR identified #8124 first (Apr 12, 6 days earlier) — both contributors credited in the salvage PR body. We went with #11974's implementation because its firing condition (_accumulated AND not current_update_visible) fires on any unsuccessful segment-break edit, while the _fallback_final_send-gated approach here only fires after the fallback mode latches (which requires _MAX_FLOOD_STRIKES=3 consecutive failures). In the actual bug scenario — austinmw's repro script — fallback isn't yet armed when the tool boundary arrives, so the broader condition was needed to cover the common case.

Thanks for the early identification and the clean diagnostic write-up.

#12414

@teknium1 teknium1 closed this Apr 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Streaming text silently dropped when tool boundary arrives during fallback mode

3 participants