herdrdev/herdr · error
ssh bridge stdin missing
Error message
ssh bridge stdin missing
What it means
The SSH bridge child was spawned with Stdio::piped() for stdin, but child.stdin was None when taken. This BrokenPipe error indicates the child's stdin handle was already consumed or unavailable, so bridging cannot upload data to ssh.
Solutions
- Retry the attach — transient pipe setup failures can occur under fd exhaustion
- Check for fd/resource limits (ulimit -n) that may cause pipe creation to silently degrade
- Report as a bug if it reproduces consistently, since spawn was configured with piped stdin
Defensive patterns
Strategy: retry
Try / catch
match bridge() {
Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe && e.to_string().contains("stdin missing") => {
bridge() // one retry; pipe setup races are transient
}
r => r,
} Prevention
- Raise fd limits (ulimit -n) on servers hosting many panes
- Retry once on BrokenPipe stdio-missing errors before reporting
When it happens
Trigger: Immediately after a successful spawn, taking .stdin on the Child returns None — typically only possible if the handle was taken twice or the Child was constructed without piped stdin; in practice this guards against internal API misuse or race in bridge setup.
Common situations: Rare internal invariant violation; more commonly surfaced when code changes reuse the Child or when platform runtime quirks drop the pipe handle.
Related errors
- ssh bridge stdout missing
- failed to start ssh bridge
- remote bridge download failed
- remote bridge upload failed
- ssh bootstrap stdin missing
AI-assisted analysis of herdrdev/herdr@f457cff4f2 (2026-08-28).
Data as JSON: /api/errors/759d6937c316da35.
Report an issue: GitHub.
Appendix: source
Thrown at src/remote/attach.rs:1841
_bridge_stop: &Arc<AtomicBool>,
) -> io::Result<()> {
let mut command = Command::new("ssh");
apply_managed_ssh_options(&mut command, ssh_options);
command
.arg("-T")
.arg(target)
.arg(remote_bridge_command(remote_herdr, session_name))
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit());
let mut child = command
.spawn()
.map_err(|err| io::Error::new(err.kind(), format!("failed to start ssh bridge: {err}")))?;
let mut child_stdin = child
.stdin
.take()
.ok_or_else(|| io::Error::new(io::ErrorKind::BrokenPipe, "ssh bridge stdin missing"))?;
let mut child_stdout = child
.stdout
.take()
.ok_or_else(|| io::Error::new(io::ErrorKind::BrokenPipe, "ssh bridge stdout missing"))?;
let mut stream_to_child = stream.try_clone()?;
let mut child_to_stream = stream;
let upload = thread::spawn(move || {
let _ = copy_flush(&mut stream_to_child, &mut child_stdin);
});
let download = thread::spawn(move || {
let _ = copy_flush(&mut child_stdout, &mut child_to_stream);
let _ = crate::ipc::shutdown_local_stream_write(&child_to_stream);
});
let status = child.wait()?;
let _ = upload.join();
let _ = download.join();View on GitHub (pinned to f457cff4f2)