Skip to content
Open
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
21 changes: 19 additions & 2 deletions src/bin/sashiko-cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1585,9 +1585,16 @@ async fn handle_local(
drop(stdin);
}

// Stream stderr for progress in a background task
// Stream stderr for progress, while keeping a bounded tail of the
// subprocess's log lines. The progress UI consumes these lines, so
// without the tail a failed review would collapse to a bare "no output"
// message with its actual cause (a stage error, an auth/quota failure)
// discarded. On failure we replay the tail so the run explains itself.
let stderr = child.stderr.take();
let stderr_handle = tokio::spawn(async move {
use std::collections::VecDeque;
const STDERR_TAIL_LINES: usize = 100;
let mut tail: VecDeque<String> = VecDeque::with_capacity(STDERR_TAIL_LINES);
if let Some(stderr) = stderr {
use tokio::io::{AsyncBufReadExt, BufReader};
let reader = BufReader::new(stderr);
Expand All @@ -1609,8 +1616,13 @@ async fn handle_local(
eprint_phase(4, 4, "Review complete.");
eprintln!();
}
if tail.len() == STDERR_TAIL_LINES {
tail.pop_front();
}
tail.push_back(line);
}
}
tail
});

// Capture stdout
Expand All @@ -1619,12 +1631,17 @@ async fn handle_local(
.await
.context("Failed to wait for review subprocess")?;

let _ = stderr_handle.await;
let stderr_tail = stderr_handle.await.unwrap_or_default();

let exit_code = output.status.code().unwrap_or(1);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();

if stdout.trim().is_empty() {
// The subprocess failed before emitting a result; replay its log
// tail so the actual cause is visible rather than swallowed.
for line in &stderr_tail {
eprintln!("{line}");
}
return Err(anyhow::anyhow!(
"Review subprocess produced no output (exit code: {})",
exit_code
Expand Down