herdrdev/herdr · error

ssh install stdin missing

Error message

ssh install stdin missing

What it means

After successfully spawning the ssh install process with stdin piped, install_herdr streams the local Herdr binary into the child's stdin via io::copy; if child.stdin is None at that point, it returns ErrorKind::BrokenPipe 'ssh install stdin missing'. This is a defensive guard for the piped-stdin contract of the spawn.

Source

Thrown at src/remote/attach.rs:511

        }
        let (tmp_path, dest_path) = parse_remote_install_paths(&output.stdout)?;

        let mut child = self
            .command()
            .arg(remote_install_stream_command(&tmp_path))
            .stdin(Stdio::piped())
            .stdout(Stdio::null())
            .stderr(Stdio::inherit())
            .spawn()
            .map_err(|err| {
                io::Error::new(err.kind(), format!("failed to start ssh install: {err}"))
            })?;

        let mut source = File::open(source_path)?;
        let copy_result = if let Some(mut stdin) = child.stdin.take() {
            io::copy(&mut source, &mut stdin).map(|_| ())
        } else {
            Err(io::Error::new(
                io::ErrorKind::BrokenPipe,
                "ssh install stdin missing",
            ))
        };
        let status = child.wait()?;
        copy_result?;

        if status.success() {
            let output = self.sh_output(&remote_install_commit_script(&tmp_path, &dest_path))?;
            if output.status.success() {
                Ok(())
            } else {
                Err(command_failed("remote install commit failed", &output))
            }
        } else {
            Err(io::Error::other(format!(
                "remote install exited with {status}"
            )))

View on GitHub (pinned to f457cff4f2)

Solutions

  1. Confirm the install Command builder sets .stdin(Stdio::piped()) before .spawn()
  2. Add a unit test asserting the spawned install child always exposes a stdin handle
  3. If hit at runtime, report it as a bug with the Herdr version and platform
  4. Check for accidental take() of stdin earlier in the same function

Example fix

// before
let mut child = install_cmd.stdout(Stdio::null()).stderr(Stdio::inherit()).spawn()?;

// after
let mut child = install_cmd.stdin(Stdio::piped())
    .stdout(Stdio::null())
    .stderr(Stdio::inherit())
    .spawn()?;
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify the install spawn keeps piped stdin before copying
// (builder-level check; nothing runtime-callable by users)

Try / catch

match install_herdr(source, remote) {
    Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => { /* stdio contract broken; verify version and report bug */ }
    other => other?,
}

Prevention

When it happens

Trigger: The ssh install child returned by spawn lacks a stdin handle despite .stdin(Stdio::piped()) being set — a spawn configuration regression or stdio setup failure in install_herdr.

Common situations: Code refactoring that removes or reorders .stdin(Stdio::piped()) in the install spawn builder; essentially a programming error rather than an environmental condition.

Related errors


AI-assisted analysis of herdrdev/herdr@f457cff4f2 (2026-08-28). Data as JSON: /api/errors/ac70c886d19900eb. Report an issue: GitHub.