herdrdev/herdr · error
ssh bootstrap stdin missing
Error message
ssh bootstrap stdin missing
What it means
sh_output spawns an ssh command with stdin piped and then writes the script to the child's stdin; if child.stdin is None (the piped stdin could not be taken, meaning spawn did not provide a stdin despite Stdio::piped()), it returns a synthetic ErrorKind::BrokenPipe 'ssh bootstrap stdin missing'. The write result is checked after wait_with_output so the ssh exit status is still observed.
Source
Thrown at src/remote/attach.rs:475
fn base_command(&self) -> Command {
let mut command = Command::new("ssh");
apply_managed_ssh_options(&mut command, self.options());
command
}
fn sh_output(&self, script: &str) -> io::Result<Output> {
let mut child = self
.command()
.arg("/bin/sh -s")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
let write_result = if let Some(mut stdin) = child.stdin.take() {
stdin.write_all(script.as_bytes())
} else {
Err(io::Error::new(
io::ErrorKind::BrokenPipe,
"ssh bootstrap stdin missing",
))
};
let output = child.wait_with_output()?;
write_result?;
Ok(output)
}
fn user_shell_output(&self, command: &str) -> io::Result<Output> {
self.command().arg(command).output()
}
fn install_herdr(&self, remote_herdr: &RemoteHerdr, source_path: &Path) -> io::Result<()> {
let output = self.sh_output(&remote_install_prepare_script(remote_herdr))?;
if !output.status.success() {
return Err(command_failed("remote install preparation failed", &output));
}View on GitHub (pinned to f457cff4f2)
Solutions
- Verify the Command builder in sh_output still sets .stdin(Stdio::piped()) before .spawn()
- Check that the ssh binary path used for spawn exists and is executable
- Handle the error by surfacing the ssh stderr output (already captured) to diagnose the spawn problem
- Add a regression test that sh_output's spawned child always has a stdin handle
Example fix
// before
let mut child = cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).spawn()?;
// after
let mut child = cmd.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?; Defensive patterns
Strategy: try-catch
Validate before calling
// Ensure the ssh Command has piped stdin before calling sh_output-based helpers assert!(cmd.get_stdin().is_piped()); // or verify builder code sets .stdin(Stdio::piped())
Try / catch
match sh_output(&cmd, script) {
Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => { /* stdio setup bug; verify spawn builder and ssh presence */ return Err(e); }
other => other?,
} Prevention
- Always pair .stdin(Stdio::piped()) with .spawn() in ssh helpers
- Verify ssh is installed and executable
- Test ssh helper paths in CI
When it happens
Trigger: Calling sh_output with ssh spawn configured .stdin(Stdio::piped()) but the returned Child has no stdin handle — practically a spawn/stdio configuration bug or an exec failure path where the pipe wasn't established.
Common situations: Refactoring the ssh spawn builder and accidentally dropping the .stdin(Stdio::piped()) call; platform-specific spawn quirks; extremely rare in practice since piped() normally guarantees a stdin handle.
Related errors
- ssh install stdin missing
- ssh bridge stdin missing
- ssh bridge stdout missing
- failed to start ssh install: {err}
- failed to start ssh bridge: {err}
AI-assisted analysis of herdrdev/herdr@f457cff4f2 (2026-08-28).
Data as JSON: /api/errors/e7e461cbdc57e2e3.
Report an issue: GitHub.