From 10883205717aafec1cddac903480c40694fc152c Mon Sep 17 00:00:00 2001 From: Brummel Date: Fri, 26 Jun 2026 21:26:12 +0200 Subject: [PATCH] fix: terminate cleanly on a broken stdout pipe across all family-emitting commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GREEN for the broken-pipe RED test (c750b9f). Rust's runtime sets SIGPIPE to SIG_IGN at startup, so a println! to a closed stdout pipe returned EPIPE and panicked ("failed printing to stdout: Broken pipe", exit 101). Restore the default SIGPIPE disposition once at the top of main() (cfg(unix), via libc::signal) so a closed pipe terminates the process cleanly on SIGPIPE (signal 13 / exit 141 — the conventional Unix CLI outcome, e.g. `yes | head`) instead of panicking. One process-level reset covers every family-emitting subcommand (sweep / mc / walkforward / runs family / generalize); the print sites are untouched (minimal fix). libc added as a direct aura-cli dependency — already transitive in Cargo.lock, the standard vetted crate for signal disposition, admitted under the per-case dependency policy. Verified by the orchestrator: the broken-pipe test passes 6/6; `aura sweep | head` exits 141 with no panic in stderr; cargo test --workspace green (0 failed); clippy --workspace --all-targets -D warnings clean. refs #146 --- Cargo.lock | 1 + crates/aura-cli/Cargo.toml | 6 ++++++ crates/aura-cli/src/main.rs | 9 +++++++++ 3 files changed, 16 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 41ca974..5d63310 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,6 +57,7 @@ dependencies = [ "aura-registry", "aura-std", "data-server", + "libc", "serde", "serde_json", ] diff --git a/crates/aura-cli/Cargo.toml b/crates/aura-cli/Cargo.toml index 27f8627..72ef1ff 100644 --- a/crates/aura-cli/Cargo.toml +++ b/crates/aura-cli/Cargo.toml @@ -29,3 +29,9 @@ serde = { workspace = true } # and the injected chart traces (window.AURA_TRACES); # admitted under the per-case dependency policy, same as aura-engine. serde_json = { workspace = true } +# libc: restore the default SIGPIPE disposition at startup (see `fn main`) so a +# closed stdout pipe (`aura sweep | head`, a closed UI pane) terminates the +# process cleanly instead of panicking the runtime's SIG_IGN default into an +# EPIPE. The standard vetted crate for signal disposition, already transitive in +# Cargo.lock; admitted under the per-case dependency policy. +libc = "0.2" diff --git a/crates/aura-cli/src/main.rs b/crates/aura-cli/src/main.rs index 8a7a786..5867f6b 100644 --- a/crates/aura-cli/src/main.rs +++ b/crates/aura-cli/src/main.rs @@ -3082,6 +3082,15 @@ const USAGE: &str = "usage: aura run [--harness ] [--real [--from ] [--to ]] [--trace ] | aura chart [--tap ] [--panels] | aura graph | aura sweep [--strategy ] [--real [--from ] [--to ]] [--name |--trace ] | aura mc [--name |--trace ] | aura mc --strategy stage1-r [--real [--from ] [--to ]] [--fast ] [--slow ] [--stop-length ] [--stop-k ] [--block-len ] [--resamples ] [--seed ] | aura walkforward [--strategy ] [--real [--from ] [--to ]] [--name |--trace ] [--fast ] [--slow ] [--stop-length ] [--stop-k ] | aura generalize [--strategy stage1-r] --real --fast --slow --stop-length --stop-k [--from ] [--to ] [--metric ] [--name ] | aura runs families | aura runs family [rank ]"; fn main() { + // Restore the default SIGPIPE disposition. Rust's runtime sets SIGPIPE to SIG_IGN + // at startup, so a write to a closed stdout pipe (`aura sweep | head`, a closed UI + // pane) returns EPIPE and panics in `println!` instead of terminating quietly on + // SIGPIPE — the conventional Unix CLI behaviour. One reset covers every + // family-emitting subcommand at once. + #[cfg(unix)] + unsafe { + libc::signal(libc::SIGPIPE, libc::SIG_DFL); + } // Collect argv and match the whole vector: every accepted form is exhaustive, // so an unexpected trailing token falls through to the usage-error path rather // than masquerading as a successful run (#16 strict reading).