From d07664b44cbe11f9893eaea00474dabd387caecf Mon Sep 17 00:00:00 2001 From: bellman Date: Wed, 3 Jun 2026 20:20:04 +0900 Subject: [PATCH] fix: keep hooks clean and close bash stdin --- .github/hooks/pre-push | 2 +- rust/crates/runtime/src/bash.rs | 51 ++++++++++++++++++++++++--------- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/.github/hooks/pre-push b/.github/hooks/pre-push index de2b9259..a6e245c0 100755 --- a/.github/hooks/pre-push +++ b/.github/hooks/pre-push @@ -13,7 +13,7 @@ cd "$repo_root" if [[ -x scripts/roadmap-check-ids.sh ]]; then echo "pre-push: scripts/roadmap-check-ids.sh" >&2 - scripts/roadmap-check-ids.sh + scripts/roadmap-check-ids.sh >&2 fi if [[ "${SKIP_CLAW_PRE_PUSH_BUILD:-}" == "1" ]]; then diff --git a/rust/crates/runtime/src/bash.rs b/rust/crates/runtime/src/bash.rs index dddf3ccf..c2a35367 100644 --- a/rust/crates/runtime/src/bash.rs +++ b/rust/crates/runtime/src/bash.rs @@ -330,20 +330,24 @@ fn prepare_tokio_command( prepare_sandbox_dirs(cwd); } - if let Some(launcher) = build_linux_sandbox_command(command, cwd, sandbox_status) { - let mut prepared = TokioCommand::new(launcher.program); - prepared.args(launcher.args); - prepared.current_dir(cwd); - prepared.envs(launcher.env); - return prepared; - } + let mut prepared = + if let Some(launcher) = build_linux_sandbox_command(command, cwd, sandbox_status) { + let mut cmd = TokioCommand::new(launcher.program); + cmd.args(launcher.args); + cmd.envs(launcher.env); + cmd + } else { + let mut cmd = TokioCommand::new("sh"); + cmd.arg("-lc").arg(command); + if sandbox_status.filesystem_active { + cmd.env("HOME", cwd.join(".sandbox-home")); + cmd.env("TMPDIR", cwd.join(".sandbox-tmp")); + } + cmd + }; - let mut prepared = TokioCommand::new("sh"); - prepared.arg("-lc").arg(command).current_dir(cwd); - if sandbox_status.filesystem_active { - prepared.env("HOME", cwd.join(".sandbox-home")); - prepared.env("TMPDIR", cwd.join(".sandbox-tmp")); - } + prepared.current_dir(cwd); + prepared.stdin(Stdio::null()); prepared } @@ -419,6 +423,27 @@ mod tests { assert_eq!(structured[0]["event"], "test.hung"); assert_eq!(structured[0]["data"]["provenance"], "bash.timeout"); } + + #[test] + fn prevents_stdin_hangs_by_redirecting_to_null() { + let output = execute_bash(BashCommandInput { + command: String::from("cat"), + timeout: Some(2_000), + description: None, + run_in_background: Some(false), + dangerously_disable_sandbox: Some(true), + namespace_restrictions: None, + isolate_network: None, + filesystem_mode: None, + allowed_mounts: None, + }) + .expect("bash command should execute cleanly"); + + assert!( + !output.interrupted, + "Command hung and was cut off by the timeout!" + ); + } } /// Maximum output bytes before truncation (16 KiB, matching upstream).