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
- Verify the command binary exists and is executable on the host (which <cmd>, check PATH inside the SSH server's environment).
- Use an absolute path to the shell/binary (e.g. /bin/sh) instead of relying on PATH resolution.
- Check file permissions and that the server user is allowed to execute the binary.
- 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
- Use absolute paths for shells/binaries in exec requests.
- Verify the target command exists in the server host's PATH/container image.
- Confirm execute permissions for the server's user account.
- Keep a minimal known-good shell (e.g. /bin/sh) available in deployment images.
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
- Failed to take child stdin
- Failed to take child stdout
- Failed to take child stderr
- Channel already has an active session
- Channel not found
AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29).
Data as JSON: /api/errors/2b3ca7085eccd536.
Report an issue: GitHub.