Skip to content

fix(stream): decode lossily instead of dropping lines on invalid UTF-8#2997

Open
albatrossflyon-coder wants to merge 1 commit into
rtk-ai:developfrom
albatrossflyon-coder:fix/utf8-line-drop-in-stream-filters
Open

fix(stream): decode lossily instead of dropping lines on invalid UTF-8#2997
albatrossflyon-coder wants to merge 1 commit into
rtk-ai:developfrom
albatrossflyon-coder:fix/utf8-line-drop-in-stream-filters

Conversation

@albatrossflyon-coder

Copy link
Copy Markdown

Summary

Fixes #2994.

run_streaming's six call sites all read child stdout/stderr with:

BufReader::new(stdout).lines().map_while(Result::ok)

BufRead::lines() returns Err for a line that isn't valid UTF-8 (OEM/ANSI
bytes from a non-English-locale Windows tool, per the issue's cp850 repro).
map_while stops iteration at the first None — so one non-UTF-8 line
doesn't just get dropped, it silently discards every line emitted after
it too. A failing build whose error text happens to contain one non-UTF-8
byte can end up looking like a clean, silent success even though the exit
code is non-zero.

Fix

Added read_lines_lossy(), which reads raw bytes via BufRead::read_until
and decodes each line with String::from_utf8_lossy (invalid bytes become
U+FFFD) instead of erroring out of the iterator. Replaced all 6
.lines().map_while(Result::ok) call sites in run_streaming with it —
this is the single shared path every filter routes through, so the fix
covers all callers at once.

rtk pipe (cmds/system/pipe_cmd.rs) already fails loudly on invalid
UTF-8 stdin rather than silently, per the issue's own note — left as-is,
out of scope here.

Testing

  • cargo test: 2448 passed, 8 ignored, 0 failed (no regressions)
  • 4 new unit tests on read_lines_lossy directly, including one that
    reproduces the exact failure mode from the issue: an ASCII line, a line
    with an embedded invalid byte, then another ASCII line — asserts all 3
    lines survive instead of the third silently vanishing
  • cargo clippy --all-targets -- -D warnings: clean
  • cargo fmt --check: clean

BufRead::lines().map_while(Result::ok) returns Err on a non-UTF-8 line
(e.g. OEM/ANSI bytes from a non-English-locale Windows tool), and
map_while stops at the first None — silently discarding every line
after the bad one too, not just the bad one. A failing build command
whose error text contains a single non-UTF-8 byte produces output that
looks like a clean success.

Add read_lines_lossy(), which reads raw bytes and decodes each line
with String::from_utf8_lossy (invalid bytes become U+FFFD) instead of
erroring, and use it at all 6 call sites in run_streaming instead of
the truncating lines()/map_while(Result::ok) pattern.

Fixes rtk-ai#2994
@CLAassistant

CLAassistant commented Jul 14, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

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.

Filters silently drop non-UTF-8 lines: shell/build errors vanish while exit code stays non-zero (non-English Windows; likely root cause of #1936)

2 participants