fix(stream): don't retry chunk timeouts after text has committed - #552
Merged
Conversation
added 2 commits
June 30, 2026 15:03
#547 made stream-chunk timeouts retryable even once content had committed, on the assumption that StreamEvent::Retry makes the consumer discard the partial. But that reset (stream.rs PROV-5) only clears the message history — it doesn't touch text already rendered to the UI. So attempt 2 re-streamed the whole response and appended a second copy, producing the duplicate output users saw when a provider stalled mid-response. Gate retry on !committed again. The #545 mid-assembly-stall case (a timeout before any text commits, e.g. a lone tool-call fragment) still retries; only timeouts after committed text now surface instead of duplicating. Also corrects the now-inaccurate "retried automatically" wording in the timeout error message and docs/config.md, which only holds for the pre-commit case.
The stall-recovery nudge ("you may have been stuck in a long reasoning
loop") fired for any pre-commit timeout. For a plain stream-chunk
timeout — "provider stalled or connection silently dropped", no tool
call open — that blame is mis-attributed: it's a transport drop, not a
reasoning loop, and the retry should just re-run clean.
Narrow is_stall_timeout to exclude the "connection silently dropped"
message. Request-level and mid-assembly timeouts (the real stall cases)
still nudge.
yogthos
pushed a commit
that referenced
this pull request
Jun 30, 2026
[AGENTS] left-panel box listing running subagents (id_short disambiguates same-profile rows), slash commands unified to a single source of truth, critic scoped/disabled for read-only and design prompts, and the 0.14.0 (#547) duplicate-output regression fixed by gating stream-chunk retry on !committed again. Plus a README note on building with newer libclang. (#548, #549, #552, #553, #554)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Assistant responses started rendering twice. A response would stream partway, the runtime notices would interleave (compaction / repair / retry banner), then the entire response would re-stream a second time. The first copy is cut off mid-sentence at the stall point — the tell-tale signature.
Root cause
#547 made stream-chunk timeouts retryable even after content had committed (
retry.rs):The premise was that
StreamEvent::Retrymakes the consumer discard the partial. But the consumer reset (stream.rsPROV-5) only clears the message history — it does not touch text already rendered to the UI. So when attempt 2 re-streamed from scratch, itsTokendeltas appended right after attempt 1's already-rendered text, producing the duplicate.Fix
Gate retry on
!committedagain:The #545 mid-assembly-stall case is preserved: a timeout before any text commits (e.g. a lone tool-call fragment, which
is_content_deltadoes not count as committed) still retries. Only timeouts after committed text now surface instead of duplicating.Also corrects the now-inaccurate "retried automatically" wording in the timeout error message (
rig_stream.rs) anddocs/config.md, which only holds for the pre-commit case.Testing
retries_timeout_after_content_committedtest (which encoded the buggy behavior) withsurfaces_timeout_after_content_committed— asserts a post-commit timeout surfaces (counter stays 1, noRetryevent, stream ends onError). Confirmed it fails on the pre-fix code and passes after.retries_timeout_before_content_committedas a regression guard for the Long Running Tasks: error: stream chunk timed out #545 case — a tool-call-only stream that times out still retries.cargo clippy --bin dirgeclean,cargo fmt --checkclean.agent::agent_loop::{retry,stream,rig_stream}suites green (48 tests), including the stall-nudge tests.Closes the regression introduced in #547.