zed-industries/zed · error
ssh process stdout capture failed
Error message
ssh process stdout capture failed
What it means
On the Unix path, MasterProcess::wait_connected takes the ssh master process's piped stdout to detect connection establishment; if the Child's stdout is None — spawned without Stdio::piped, or already consumed by an earlier take() — this bail fires. It is an internal invariant about how the ssh master was spawned rather than a network condition.
Source
Thrown at crates/remote/src/transport/ssh.rs:217
.kill_on_drop(true)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.env("SSH_ASKPASS_REQUIRE", "force")
.env("SSH_ASKPASS", askpass_script_path)
.args(additional_args)
.args(args);
master_process.arg(format!("ControlPath={}", socket_path.display()));
let process = master_process.arg(&destination).spawn()?;
Ok(MasterProcess { process })
}
pub async fn wait_connected(&mut self) -> Result<()> {
let Some(mut stdout) = self.process.stdout.take() else {
anyhow::bail!("ssh process stdout capture failed");
};
let mut output = Vec::new();
stdout.read_to_end(&mut output).await?;
Ok(())
}
}
#[cfg(windows)]
impl MasterProcess {
const CONNECTION_ESTABLISHED_MAGIC: &str = "ZED_SSH_CONNECTION_ESTABLISHED";
pub fn new(
askpass_script_path: &std::ffi::OsStr,
askpass_socket_path: &std::ffi::OsStr,
additional_args: Vec<String>,
destination: &str,
) -> Result<Self> {View on GitHub (pinned to f4178619ac)
Solutions
- Spawn the master process with piped stdout: add .stdout(Stdio::piped()) (and keep stderr piped for diagnostics) to the Command builder
- Call wait_connected exactly once per MasterProcess; the Option::take design already enforces single consumption if honored
- If wrapping ssh via a script or ProxyCommand, ensure it forwards the child's stdout instead of closing it
Example fix
// before
let process = master_process.arg(&destination).spawn()?;
// after
use std::process::Stdio;
let process = master_process
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.arg(&destination)
.spawn()?; Defensive patterns
Strategy: try-catch
Try / catch
match master.wait_connected().await {
Err(e) if e.to_string().contains("stdout capture failed") => {
// spawn bug: master process must be built with .stdout(Stdio::piped()); fix the builder
Err(e)
}
other => other,
} Prevention
- Always spawn the ssh master with piped stdout and stderr
- Call wait_connected exactly once per MasterProcess
- Never wrap ssh in a script that closes stdout
When it happens
Trigger: Calling wait_connected twice on the same MasterProcess (the first take() emptied the Option), or constructing the master ssh command without .stdout(Stdio::piped()) in the builder.
Common situations: Local Zed source modifications that drop the piped stdout from the master_process builder; two tasks racing to call wait_connected; an ssh wrapper or ProxyCommand that closes or redirects stdout.
Related errors
- build ids may only contain lowercase letters, numbers, '.',
- registry dataset requires a name
- cannot open repository on disconnected remote machine
- When using the `stdio` transport, the path to a debug adapte
- unknown uname: {uname:?}
AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20).
Data as JSON: /api/errors/084aad1e5857ee62.
Report an issue: GitHub.