BloopAI/vibe-kanban · error · ExecutorError
Child process has no stdin
Error message
Child process has no stdin
What it means
bootstrap_acp_connection also takes the child's stdin to send ACP requests. If the child was spawned without a piped stdin, or its stdin was already taken, this error is returned. It mirrors the stdout check immediately above it in the same function.
Source
Thrown at crates/executors/src/executors/acp/harness.rs:209
cwd: PathBuf,
existing_session: Option<String>,
prompt: String,
exit_signal: Option<tokio::sync::oneshot::Sender<ExecutorExitResult>>,
session_namespace: String,
model: Option<String>,
mode: Option<String>,
approvals: Option<std::sync::Arc<dyn ExecutorApprovalService>>,
cancel: CancellationToken,
) -> Result<(), ExecutorError> {
// Take child's stdio for ACP wiring
let orig_stdout = child.inner().stdout.take().ok_or_else(|| {
ExecutorError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Child process has no stdout",
))
})?;
let orig_stdin = child.inner().stdin.take().ok_or_else(|| {
ExecutorError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Child process has no stdin",
))
})?;
// Create a fresh stdout pipe for logs
let writer = crate::stdout_dup::create_stdout_pipe_writer(child)?;
let shared_writer = Arc::new(tokio::sync::Mutex::new(writer));
let (log_tx, mut log_rx) = mpsc::unbounded_channel::<String>();
// Spawn log -> stdout writer task
tokio::spawn(async move {
while let Some(line) = log_rx.recv().await {
let mut data = line.into_bytes();
data.push(b'\n');
let mut w = shared_writer.lock().await;
let _ = w.write_all(&data).await;
}View on GitHub (pinned to 4deb7eca8f)
Solutions
- Ensure the child is spawned with stdin(Stdio::piped()) as well as stdout.
- Call bootstrap_acp_connection only once per child process.
- Check that no wrapper script or prior harness consumed the agent's stdin.
- Review the executor spawn path for the ACP configuration to confirm both pipes are requested.
Example fix
// before let child = cmd.spawn()?; // stdin defaults to inherit // after cmd.stdin(Stdio::piped()); let child = cmd.spawn()?;
Defensive patterns
Strategy: type-guard
Type guard
fn has_piped_stdio(child: &Child) -> bool {
child.stdin.is_some() && child.stdout.is_some()
}
if !has_piped_stdio(&child) {
anyhow::bail!("child must be spawned with piped stdin/stdout");
} Try / catch
match bootstrap_acp_connection(...).await {
Err(ExecutorError::Io(e)) if e.kind() == ErrorKind::NotFound
&& e.to_string().contains("no stdin") => {
// respawn the agent with Stdio::piped() stdin
}
other => other,
} Prevention
- Set cmd.stdin(Stdio::piped()) whenever the executor runs in ACP mode.
- Avoid wrapper scripts that read or redirect the agent's stdin.
- Guard against double bootstrap — take() consumes the pipe the first time.
When it happens
Trigger: Calling bootstrap_acp_connection on a child spawned without Stdio::piped() for stdin, or a second bootstrap call after stdin was consumed by the first.
Common situations: Spawning the agent with stdin inherited from an interactive terminal or set to null; double initialization of the ACP harness; wrapper scripts consuming stdin before the protocol starts.
Related errors
- Child process has no stdout
- Timeout: process took more than 30 seconds to start
- Cannot run script while another process is running
- Child process not found for execution
- OpenCode startup error: {err}
AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29).
Data as JSON: /api/errors/b3b043fd0ecc199c.
Report an issue: GitHub.