From 97078b638e35d820528138eedde9b3d45cf8dd4b Mon Sep 17 00:00:00 2001 From: Mykhailo Chalyi Date: Fri, 27 Mar 2026 23:28:58 +0000 Subject: [PATCH] fix(interpreter): check errexit_suppressed in execute_script_body set -e incorrectly exited when a for/while/until loop body ended with an AND-OR chain (e.g. [[ test ]] && cmd) that returned non-zero. Check result.errexit_suppressed in execute_script_body to suppress errexit for compound commands whose non-zero exit came from && or ||. Closes #873 --- crates/bashkit/src/interpreter/mod.rs | 3 +-- crates/bashkit/tests/set_e_and_or_tests.rs | 24 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/crates/bashkit/src/interpreter/mod.rs b/crates/bashkit/src/interpreter/mod.rs index 220a482b..98eb756c 100644 --- a/crates/bashkit/src/interpreter/mod.rs +++ b/crates/bashkit/src/interpreter/mod.rs @@ -1152,8 +1152,7 @@ impl Interpreter { // errexit (set -e): stop on non-zero exit for top-level simple commands. // List commands handle errexit internally (with && / || chain awareness). // Negated pipelines (! cmd) explicitly handle the exit code. - // Compound commands (for/while/until) propagate errexit_suppressed when - // their body ends with an AND-OR chain failure. + // Compound commands propagate errexit_suppressed from inner AND-OR chains. if self.is_errexit_enabled() && exit_code != 0 { let suppressed = matches!(command, Command::List(_)) || matches!(command, Command::Pipeline(p) if p.negated) diff --git a/crates/bashkit/tests/set_e_and_or_tests.rs b/crates/bashkit/tests/set_e_and_or_tests.rs index 0ab415e3..c6a877b9 100644 --- a/crates/bashkit/tests/set_e_and_or_tests.rs +++ b/crates/bashkit/tests/set_e_and_or_tests.rs @@ -191,6 +191,30 @@ echo "SHOULD NOT APPEAR" assert!(!result.stdout.contains("SHOULD NOT APPEAR")); } +/// set -e: && chain failure at end of for loop body should NOT exit (issue #873) +#[tokio::test] +async fn set_e_and_chain_at_end_of_for_body() { + let mut bash = Bash::new(); + let result = bash + .exec( + r#" +set -euo pipefail +result="" +for src in yes no; do + [[ "${src}" == "yes" ]] && result="${src}" +done +echo "result: ${result}" +"#, + ) + .await + .unwrap(); + assert!( + result.stdout.contains("result: yes"), + "should print 'result: yes' but got: {:?}", + result.stdout + ); +} + /// set -e should still exit on non-AND-OR failures #[tokio::test] async fn set_e_exits_on_plain_failure() {