BloopAI/vibe-kanban · error
Failed to take child stdin
Error message
Failed to take child stdin
What it means
Immediately after a successful spawn, the code takes ownership of the child's stdin handle. std::process::Child::stdin returns None only if stdin was not piped (stdin(Stdio::piped()) was not set when building the command). This error is therefore an internal invariant violation: the command was constructed without piped stdin while the stdio session requires it.
Source
Thrown at crates/embedded-ssh/src/handler.rs:98
cmd.stdin(Stdio::piped());
cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::piped());
cmd.env("TERM", "xterm-256color");
for (k, v) in env {
cmd.env(k, v);
}
if let Ok(home) = std::env::var("HOME") {
cmd.current_dir(home);
}
let mut child = cmd
.spawn()
.map_err(|e| anyhow::anyhow!("Failed to spawn stdio command: {e}"))?;
let stdin = child
.stdin
.take()
.ok_or_else(|| anyhow::anyhow!("Failed to take child stdin"))?;
let stdout = child
.stdout
.take()
.ok_or_else(|| anyhow::anyhow!("Failed to take child stdout"))?;
let stderr = child
.stderr
.take()
.ok_or_else(|| anyhow::anyhow!("Failed to take child stderr"))?;
let (writer_tx, mut writer_rx) = mpsc::channel::<Vec<u8>>(64);
tokio::spawn(async move {
let mut stdin = stdin;
while let Some(data) = writer_rx.recv().await {
if stdin.write_all(&data).await.is_err() {
break;
}
if stdin.flush().await.is_err() {
break;View on GitHub (pinned to 4deb7eca8f)
Solutions
- Ensure the command is built with .stdin(Stdio::piped()) before spawn in spawn_stdio_session.
- Confirm no later code overrides stdin (e.g. Stdio::inherit/null) after it was piped.
- If using tokio::process, verify you're taking stdin before the child is polled to completion (handles aren't auto-closed here since it's std, but keep ordering sane).
Example fix
// before let mut cmd = Command::new(shell); cmd.stdin(Stdio::null()); // after let mut cmd = Command::new(shell); cmd.stdin(Stdio::piped()); cmd.stdout(Stdio::piped()); cmd.stderr(Stdio::piped());
Defensive patterns
Strategy: validation
Validate before calling
// assert construction configures piped stdio (in handler tests)
let mut cmd = Command::new(shell);
assert_piped_stdio(&mut cmd);
fn assert_piped_stdio(cmd: &mut Command) {
cmd.stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped());
} Prevention
- Always configure Stdio::piped() for stdin, stdout, and stderr when building stdio-session commands.
- Add a test that runs spawn_stdio_session on a trivial command to catch construction regressions.
- Don't share command-construction helpers with PTY paths that use different Stdio settings.
- Treat any of the three 'Failed to take child *' errors as internal bugs — fix construction, not callers.
When it happens
Trigger: spawn_stdio_session's command builder omits .stdin(Stdio::piped()) (or a code change replaced it), so child.stdin.take() returns None even though spawn succeeded.
Common situations: Practically only seen after modifying the handler's command construction or when a refactor changed which Stdio configuration is applied; end users cannot trigger it via SSH requests.
Related errors
- Failed to take child stdout
- Failed to take child stderr
- Failed to spawn stdio command: {e}
- Channel already has an active session
- Channel not found
AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29).
Data as JSON: /api/errors/dd5f6b282d680f88.
Report an issue: GitHub.