diff --git a/hive-bash-mcp/src/bin/mcp.rs b/hive-bash-mcp/src/bin/mcp.rs index 101882aa..f31ee081 100644 --- a/hive-bash-mcp/src/bin/mcp.rs +++ b/hive-bash-mcp/src/bin/mcp.rs @@ -151,7 +151,7 @@ fn render_bash_status(id: &str, resp: Result, waited: bool) -> S #[derive(Debug, Deserialize, JsonSchema)] struct BashRunArgs { - /// Shell command to run (passed to `sh -c`). + /// Shell command to run (passed to `bash -c`). cmd: String, /// Timeout in seconds. Defaults to `None` (no timeout) — task runs until /// natural exit. Pass an explicit value to kill the task after N seconds diff --git a/hive-bash-mcp/src/runner.rs b/hive-bash-mcp/src/runner.rs index 485df2ff..bc816d6f 100644 --- a/hive-bash-mcp/src/runner.rs +++ b/hive-bash-mcp/src/runner.rs @@ -1,4 +1,4 @@ -//! Bash subprocess runner: spawns `sh -c ` tasks, writes status +//! Bash subprocess runner: spawns `bash -c ` tasks, writes status //! files under `harness/bash-tasks/`, and fires hyperhive wake signals //! on completion. Mirrors the logic previously embedded in `hive-ag3nt`. //! @@ -400,8 +400,12 @@ async fn run_task(mut task: TaskFile, socket: &Path) { send_wake(socket, &id, &summary, Some((out_snippet, err_snippet))).await; } -/// Run `sh -c cmd`, streaming output to files. Returns `(exit_code, timed_out)`. +/// Run `bash -c cmd`, streaming output to files. Returns `(exit_code, timed_out)`. /// `timeout_secs = None` means no timeout — run until natural exit. +/// +/// Tasks run under `bash`, not `sh`: on NixOS `/bin/sh` is bash in POSIX +/// mode, which disables bashisms (arrays, `[[ … ]]`, `local`, process +/// substitution, …). Agents write bash, so we invoke `bash` directly. async fn exec_cmd( cmd: &str, out_path: &Path, @@ -411,7 +415,7 @@ async fn exec_cmd( use tokio::process::Command; // SAFETY: `nice` is async-signal-safe and modifies only the calling // process's scheduling priority before exec. No allocations, no locks. - let mut cmd_builder = Command::new("sh"); + let mut cmd_builder = Command::new("bash"); cmd_builder .arg("-c") .arg(cmd) diff --git a/nix/templates/harness-base.nix b/nix/templates/harness-base.nix index 89cda1e9..ecb00ca8 100644 --- a/nix/templates/harness-base.nix +++ b/nix/templates/harness-base.nix @@ -1419,14 +1419,14 @@ in systemd.services.hive-bash-daemon = { description = "bash task runner daemon for hive-bash-mcp"; wantedBy = [ "multi-user.target" ]; - # The daemon runs every bash task via `Command::new("sh")` and the + # The daemon runs every bash task via `Command::new("bash")` and the # commands themselves (hive-forge, git, jq, …) resolve from PATH. # Pre-split this ran inside hive-ag3nt.service and inherited the - # agent's PATH; the standalone daemon needs the same or `sh` itself + # agent's PATH; the standalone daemon needs the same or `bash` itself # isn't found (spawn fails with ENOENT, the task is marked done in # 0s with no output / no .out/.err). Mirror the harness unit's PATH: # NixOS appends `/bin` to each entry → /run/wrappers/bin (setuid - # sudo) + /run/current-system/sw/bin (sh, coreutils, hive-forge, …). + # sudo) + /run/current-system/sw/bin (bash, coreutils, hive-forge, …). path = [ "/run/wrappers" "/run/current-system/sw"