Skip to content

fix(stream): replace map_while(Result::ok) with from_utf8_lossy to prevent silent output truncation - #2049

Closed
rosschurchill wants to merge 1 commit into
rtk-ai:developfrom
rosschurchill:fix/c06-utf8-lossy-stream
Closed

rosschurchill wants to merge 1 commit into
rtk-ai:developfrom
rosschurchill:fix/c06-utf8-lossy-stream

Conversation

@rosschurchill

Copy link
Copy Markdown

The 6 lines().map_while(Result::ok) sites in stream.rs silently terminate iteration on the first non-UTF-8 byte. A single bad byte in tool output means everything after it is silently dropped — the LLM never sees it, with no warning.

Replaced all 6 sites with BufRead::read_until + String::from_utf8_lossy, which replaces invalid bytes with U+FFFD and continues. The LLM sees a replacement character instead of losing the rest of the output.

Includes a regression test with a byte sequence containing \xFF\xFE mid-stream to verify all lines are preserved.

Free to use as-is.

…tes in stream.rs

lines().map_while(Result::ok) silently terminates the iterator on the first
non-UTF-8 byte, causing all output after a bad byte to disappear from the
LLM's view with no warning. Replaced all 6 sites with BufRead::read_until +
String::from_utf8_lossy so invalid bytes become U+FFFD replacement chars and
subsequent lines continue to flow through. Adds regression test with a
\xFF\xFE fixture asserting all 3 lines are preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

thehoff added a commit to thehoff/contextcrawler that referenced this pull request Jun 5, 2026
The StdinMode::Filter path read child stdin with
BufRead::lines().map_while(Result::ok), which silently stops at the
first invalid-UTF-8 byte — everything after it never reaches the
child process. The stdout/stderr paths were already fixed with the
byte-read + from_utf8_lossy pattern (for_each_line); this brings the
stdin path in line.

Extracts run_stdin_filter() so the lossy path is unit-testable
without real io::stdin(). Invalid bytes become U+FFFD; the stream
never truncates. Behavioural note: write errors after early child
exit now drain stdin to EOF instead of breaking early (StdinMode::
Filter is a future-API path, not yet used in production).

Council triage 2026-06-02 HIGH (flagged independently by all three
voices: Codex, agy, mmax). Upstream ref: rtk PR rtk-ai#2049 pattern.

TDD: red (E0425 then line-after-bad-bytes assertion), green (62/62
stream tests). Re-implemented in main checkout after the original
lane's work was destroyed by the worktree-hijack bug (task #11).
Landed with Hoff-authorised COUNCIL_OK (council CLI blocked by
tasks #10/#11).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@KuSh

KuSh commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Hi @rosschurchill, thanks so much for this contribution! It looks like #2997 just got merged with similar work, so we're going to close this one out.

@KuSh KuSh closed this Aug 12, 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.

3 participants