{"record":{"id":"e7e461cbdc57e2e3","repo":"herdrdev/herdr","slug":"ssh-bootstrap-stdin-missing","errorCode":null,"errorMessage":"ssh bootstrap stdin missing","messagePattern":"ssh bootstrap stdin missing","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/remote/attach.rs","lineNumber":475,"sourceCode":"    fn base_command(&self) -> Command {\n        let mut command = Command::new(\"ssh\");\n        apply_managed_ssh_options(&mut command, self.options());\n        command\n    }\n\n    fn sh_output(&self, script: &str) -> io::Result<Output> {\n        let mut child = self\n            .command()\n            .arg(\"/bin/sh -s\")\n            .stdin(Stdio::piped())\n            .stdout(Stdio::piped())\n            .stderr(Stdio::piped())\n            .spawn()?;\n\n        let write_result = if let Some(mut stdin) = child.stdin.take() {\n            stdin.write_all(script.as_bytes())\n        } else {\n            Err(io::Error::new(\n                io::ErrorKind::BrokenPipe,\n                \"ssh bootstrap stdin missing\",\n            ))\n        };\n        let output = child.wait_with_output()?;\n        write_result?;\n        Ok(output)\n    }\n\n    fn user_shell_output(&self, command: &str) -> io::Result<Output> {\n        self.command().arg(command).output()\n    }\n\n    fn install_herdr(&self, remote_herdr: &RemoteHerdr, source_path: &Path) -> io::Result<()> {\n        let output = self.sh_output(&remote_install_prepare_script(remote_herdr))?;\n        if !output.status.success() {\n            return Err(command_failed(\"remote install preparation failed\", &output));\n        }","sourceCodeStart":457,"sourceCodeEnd":493,"githubUrl":"https://github.com/herdrdev/herdr/blob/f457cff4f2648eee85d176f8a41861241d4e8428/src/remote/attach.rs#L457-L493","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nlet mut child = cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).spawn()?;\n\n// after\nlet mut child = cmd.stdin(Stdio::piped())\n    .stdout(Stdio::piped())\n    .stderr(Stdio::piped())\n    .spawn()?;","handlingStrategy":"try-catch","validationCode":"// Ensure the ssh Command has piped stdin before calling sh_output-based helpers\nassert!(cmd.get_stdin().is_piped()); // or verify builder code sets .stdin(Stdio::piped())","typeGuard":null,"tryCatchPattern":"match sh_output(&cmd, script) {\n    Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => { /* stdio setup bug; verify spawn builder and ssh presence */ return Err(e); }\n    other => other?,\n}","preventionTips":["Always pair .stdin(Stdio::piped()) with .spawn() in ssh helpers","Verify ssh is installed and executable","Test ssh helper paths in CI"],"tags":["ssh","remote-attach","stdio","spawn","broken-pipe"],"backgroundTag":"spawned-process-stdin-missing","analyzedSha":"f457cff4f2648eee85d176f8a41861241d4e8428","analyzedAt":"2026-08-28T15:41:09.197Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}