fix(client): fail closed on SSE UTF-8 split across HTTP/2 DATA — shared decoder + tail flush (#5374) - #5468
Conversation
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
Claude finished @Hmbown's task in 4m 11s —— View job Reviewing PR #5468
SummaryThis is a solid, well-scoped fix. It adds a single shared strict decoder ( Notable genuine bug fix buried in the Findings1. Test coverage gap — Anthropic/Responses tail-flush path has no direct test ( Notes (not blocking)
I was unable to run |
Summary
Closes #5374 — DeepSeek Flash on macOS showed garbled streaming agent text (U+FFFD / broken CJK) because HTTP/2 DATA frames can split a multi-byte UTF-8 character across chunks and the SSE readers decoded per chunk.
What main already had (19c4d1d):
take_sse_line/flush_sse_linebuffer raw bytes and decode only complete\n-terminated lines with strictstr::from_utf8; a genuinely invalid line returnsInvalidSseUtf8instead of substituting U+FFFD.What this PR adds on top of that baseline:
decode_sse_line_bytes— the one shared strict decoder used by bothtake_sse_lineandflush_sse_line;InvalidSseUtf8slimmed tovalid_up_towith a single Display form.next_sse_line(buffer, at_end)— complete-line-or-tail helper so every dialect flushes the unterminated stream-end tail through the same fail-closed path.chat.rs): newdecode_failedflag; a UTF-8 error on a complete line or on the tail flush yieldsErr(InvalidSseUtf8)and skips the post-loop frame parse so nothing after the error reaches the transcript.anthropic.rs) and Responses (responses.rs): read loop switched to anendedstate so the stream-end tail is flushed vianext_sse_line; decode errors yieldErrand return.SseLineDecoder(#[cfg(test)]) — incremental raw-byte line assembler used by the new decoder tests.Tests
crates/tui/src/client.rs:take_sse_line_reassembles_cjk_and_rejects_invalid_bytes,flush_sse_line_reassembles_cjk_and_rejects_invalid_bytes,decode_sse_line_bytes_rejects_invalid_without_replacement; existing take/flush tests updated for the slimmed error type (byte-split positions computed viamid_char_split).crates/tui/src/client/chat/tests/stream_decoder.rs:decoder_reassembles_cjk_split_across_byte_chunks,decoder_reassembles_emoji_and_cjk_fed_one_byte_at_a_time,decoder_rejects_invalid_sse_bytes_without_replacement— drive the Chat Completions frame parser over raw byte chunks split mid-character.Relation to #5404
Supersedes draft #5404 (cursor branch, based on
5ac75add4, before main's fail-closed baseline landed). This branch is rebased onto current main containing 19c4d1d, so it only carries the delta (shared decoder, tail flush, per-dialect propagation, tests). #5404 is left open for the owner to close.Testing
cargo fmt --all -- --checkcargo clippy --workspace --all-targets --all-features --locked(CI)cargo test --workspace --all-features --locked(CI)Checklist