From 1eabc436e0ec10402bf749df3b163ec9e45aeabf Mon Sep 17 00:00:00 2001 From: Mykhailo Chalyi Date: Fri, 3 Apr 2026 02:37:26 +0000 Subject: [PATCH] fix(interpreter): contain ${var:?msg} error within subshell boundary Closes #961 --- crates/bashkit/src/interpreter/mod.rs | 18 +++++++++++------- .../tests/spec_cases/bash/subshell.test.sh | 8 ++++++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/crates/bashkit/src/interpreter/mod.rs b/crates/bashkit/src/interpreter/mod.rs index 81df30dc..58aa7079 100644 --- a/crates/bashkit/src/interpreter/mod.rs +++ b/crates/bashkit/src/interpreter/mod.rs @@ -1438,13 +1438,17 @@ impl Interpreter { self.coproc_buffers = saved_coproc; self.memory_budget = saved_memory_budget; - // Consume Exit control flow at subshell boundary — exit only - // terminates the subshell, not the parent shell. - if let Ok(ref mut res) = result - && let ControlFlow::Exit(code) = res.control_flow - { - res.exit_code = code; - res.control_flow = ControlFlow::None; + // Consume Exit and Return control flow at subshell boundary — + // they only terminate the subshell, not the parent shell. + // Return is used by ${var:?msg} error handling and nounset errors. + if let Ok(ref mut res) = result { + match res.control_flow { + ControlFlow::Exit(code) | ControlFlow::Return(code) => { + res.exit_code = code; + res.control_flow = ControlFlow::None; + } + _ => {} + } } result diff --git a/crates/bashkit/tests/spec_cases/bash/subshell.test.sh b/crates/bashkit/tests/spec_cases/bash/subshell.test.sh index bc8300ab..43e7d56f 100644 --- a/crates/bashkit/tests/spec_cases/bash/subshell.test.sh +++ b/crates/bashkit/tests/spec_cases/bash/subshell.test.sh @@ -129,3 +129,11 @@ echo "$@" x y a b c ### end + +### parameter_error_in_subshell_contained +# ${var:?msg} error in subshell should not kill parent +(unset NOSUCHVAR; echo "${NOSUCHVAR:?gone}" 2>/dev/null) +echo "survived: $?" +### expect +survived: 1 +### end