BloopAI/vibe-kanban · error

Failed to spawn stdio command: {e}

Error message

Failed to spawn stdio command: {e}

What it means

After building the stdio command (respecting env and HOME cwd), spawn_stdio_session calls Command::spawn(). If the OS fails to start the child process, the io::Error is wrapped into this anyhow error. It means the process never started — nothing to do with the SSH channel itself.

Source

Thrown at crates/embedded-ssh/src/handler.rs:93

                // Non-interactive shell reading commands from stdin.
                cmd.arg("-s");
            }
        }

        cmd.stdin(Stdio::piped());
        cmd.stdout(Stdio::piped());
        cmd.stderr(Stdio::piped());
        cmd.env("TERM", "xterm-256color");
        for (k, v) in env {
            cmd.env(k, v);
        }
        if let Ok(home) = std::env::var("HOME") {
            cmd.current_dir(home);
        }

        let mut child = cmd
            .spawn()
            .map_err(|e| anyhow::anyhow!("Failed to spawn stdio command: {e}"))?;

        let stdin = child
            .stdin
            .take()
            .ok_or_else(|| anyhow::anyhow!("Failed to take child stdin"))?;
        let stdout = child
            .stdout
            .take()
            .ok_or_else(|| anyhow::anyhow!("Failed to take child stdout"))?;
        let stderr = child
            .stderr
            .take()
            .ok_or_else(|| anyhow::anyhow!("Failed to take child stderr"))?;

        let (writer_tx, mut writer_rx) = mpsc::channel::<Vec<u8>>(64);
        tokio::spawn(async move {
            let mut stdin = stdin;
            while let Some(data) = writer_rx.recv().await {

View on GitHub (pinned to 4deb7eca8f)

Solutions

  1. Verify the command binary exists and is executable on the host (which <cmd>, check PATH inside the SSH server's environment).
  2. Use an absolute path to the shell/binary (e.g. /bin/sh) instead of relying on PATH resolution.
  3. Check file permissions and that the server user is allowed to execute the binary.
  4. Log the underlying io::Error (embedded in the message) to distinguish NotFound vs PermissionDenied and fix accordingly.

Example fix

// before
channel.exec("my-tool --version"); // my-tool not on PATH
// after
channel.exec("/usr/local/bin/my-tool --version"); // or install my-tool on the host
Defensive patterns

Strategy: try-catch

Validate before calling

// check the binary resolves before exec
let ok = std::process::Command::new("sh")
    .args(["-c", "command -v my-tool"])
    .output()
    .map(|o| o.status.success())
    .unwrap_or(false);

Try / catch

match handler.spawn_stdio_session(session, channel_id, env).await {
    Ok(()) => {},
    Err(e) if e.to_string().contains("Failed to spawn stdio command") => {
        let msg = e.to_string();
        if msg.contains("No such file") {
            // report 'command not found' to the SSH client
        } else if msg.contains("Permission denied") {
            // report permissions problem
        }
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: The requested shell/exec command binary does not exist or is not executable, PATH lacks the binary, permission is denied, or the exec'ed program string is malformed so the spawned command cannot be launched.

Common situations: Minimal container images without the user's login shell (e.g. no /bin/bash), restricted environments with missing PATH, users exec-ing custom commands not installed on the host, or a HOME dir set to a nonexistent path (though that only affects cwd).

Related errors


AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29). Data as JSON: /api/errors/2b3ca7085eccd536. Report an issue: GitHub.