fix(cli): fail continuous runs on peer closure

Keep PeerClosed as a distinct successful library outcome while treating it as an error at the CLI and TUI boundary for uninterrupted continuous runs.

Track aggregate peer-close outcomes independently of bounded recent history and cover finite, continuous, grouped, mixed-failure, interruption, and TUI status behavior.
This commit is contained in:
Nils Andreas Svee committed 2026-07-28 10:23:47 +07:00
1 parent 8ea4c2f8a4
commit 58d11f1dec
7 files changed
+358 -19

No files matched your search

+29 -9
View File
@@ -23,7 +23,9 @@ use super::{
#[cfg(feature = "stats")]
use crate::shared::client::expected_probe_count;
use crate::shared::client::{
is_shutdown_requested, session::should_print_final_summary, ClientSession,
is_shutdown_requested,
session::{peer_close_run_error, should_print_final_summary},
ClientSession,
};
#[cfg(feature = "stats")]
@@ -152,6 +154,12 @@ pub fn run_stream(
print_events_with_stats(&mut stream_output, &events, Some(target_label), &mut stats)?;
#[cfg(not(feature = "stats"))]
print_events_with_stats(&mut stream_output, &events, Some(target_label))?;
interrupted |= is_shutdown_requested(shutdown_requested);
let peer_close_error = peer_close_run_error(
continuous,
interrupted,
if session.is_peer_closed() { 1 } else { 0 },
);
let print_final_summary = should_print_final_summary(continuous, interrupted);
stream_output.print_final_summary = print_final_summary;
stream_output.show_running_only_summary_note = continuous && interrupted && print_final_summary;
@@ -160,6 +168,9 @@ pub fn run_stream(
#[cfg(not(feature = "stats"))]
stream_output.print_summary()?;
stream_output.out.flush()?;
if let Some(error) = peer_close_error {
return Err(error.into());
}
Ok(())
}
@@ -403,9 +414,7 @@ fn run_group_stream(
eprintln!("{warning}");
}
if outcome.end_reason == ManagedGroupEndReason::Cancelled && !interrupted {
return Err("managed client group was cancelled".into());
}
interrupted |= is_shutdown_requested(shutdown_requested);
for target in &outcome.targets {
if terminal_targets.insert(target.id.as_str().to_owned()) {
report_target_failure(target);
@@ -413,11 +422,19 @@ fn run_group_stream(
}
let successful_targets = outcome.successful_target_outcomes;
let failed_targets = outcome.failed_target_outcomes;
if !interrupted && successful_targets == 0 && failed_targets > 0 {
return Err(
format!("no managed target completed successfully ({failed_targets} failed)").into(),
);
}
let terminal_error = if outcome.end_reason == ManagedGroupEndReason::Cancelled && !interrupted {
Some("managed client group was cancelled".to_owned())
} else if let Some(error) =
peer_close_run_error(continuous, interrupted, outcome.peer_closed_target_outcomes)
{
Some(error)
} else if !interrupted && successful_targets == 0 && failed_targets > 0 {
Some(format!(
"no managed target completed successfully ({failed_targets} failed)"
))
} else {
None
};
let print_final_summary = should_print_final_summary(continuous, interrupted);
stream_output.print_final_summary = print_final_summary;
@@ -437,6 +454,9 @@ fn run_group_stream(
stream_output.print_summary()?;
}
stream_output.out.flush()?;
if let Some(error) = terminal_error {
return Err(error.into());
}
Ok(())
}