Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

52 changes: 33 additions & 19 deletions src/executor/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ use super::SafetyAnalyzer;
use crate::config::Config;
use anyhow::Result;
use colored::Colorize;
use std::io::Write;
use std::process::Stdio;
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::io::AsyncReadExt;
use tokio::process::Command;

/// Command executor with safety checks
Expand Down Expand Up @@ -50,30 +51,43 @@ impl CommandExecutor {

let exit_code = if follow {
// Stream output in real-time
let stdout = child.stdout.take().unwrap();
let stderr = child.stderr.take().unwrap();
let mut stdout = child.stdout.take().unwrap();
let mut stderr = child.stderr.take().unwrap();

let stdout_reader = BufReader::new(stdout);
let stderr_reader = BufReader::new(stderr);

let mut stdout_lines = stdout_reader.lines();
let mut stderr_lines = stderr_reader.lines();
let mut stdout_buf = [0u8; 1024];
let mut stderr_buf = [0u8; 1024];
let mut stdout_done = false;
let mut stderr_done = false;

// Process output
loop {
while !stdout_done || !stderr_done {
tokio::select! {
line = stdout_lines.next_line() => {
match line {
Ok(Some(line)) => println!("{}", line),
Ok(None) => break,
Err(e) => eprintln!("{}: {}", "Error".red(), e),
res = stdout.read(&mut stdout_buf), if !stdout_done => {
match res {
Ok(0) => stdout_done = true,
Ok(n) => {
print!("{}", String::from_utf8_lossy(&stdout_buf[..n]));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve UTF-8 boundaries when streaming chunked output

Decoding each read() chunk independently with String::from_utf8_lossy can corrupt valid UTF-8 output whenever a multibyte character is split across chunk boundaries. In that case, this path emits replacement characters () and loses the original text, so commands that print non-ASCII output (localized package managers, Unicode filenames, etc.) can display garbled prompts/logs. The same pattern is used for both stdout and stderr, so both streams are affected.

Useful? React with 👍 / 👎.

std::io::stdout().flush().unwrap_or(());
}
Err(e) => {
eprintln!("{}: {}", "Error".red(), e);
stdout_done = true;
}
}
}
line = stderr_lines.next_line() => {
match line {
Ok(Some(line)) => eprintln!("{}", line.red()),
Ok(None) => {}
Err(e) => eprintln!("{}: {}", "Error".red(), e),
res = stderr.read(&mut stderr_buf), if !stderr_done => {
match res {
Ok(0) => stderr_done = true,
Ok(n) => {
// We print stderr in red but without trailing newline if not present
let text = String::from_utf8_lossy(&stderr_buf[..n]);
eprint!("{}", text.red());
std::io::stderr().flush().unwrap_or(());
}
Err(e) => {
eprintln!("{}: {}", "Error".red(), e);
stderr_done = true;
}
}
}
}
Expand Down
Loading