BloopAI/vibe-kanban · error · ExecutorError
Child process has no stdout
Error message
Child process has no stdout
What it means
bootstrap_acp_connection rewires the executor child process's stdio to speak ACP (Agent Client Protocol). It takes ownership of the child's stdout; if the child was spawned without a piped stdout (piped to null, inherited, or already taken), this error is returned because the harness cannot attach the protocol transport.
Source
Thrown at crates/executors/src/executors/acp/harness.rs:203
})
}
#[allow(clippy::too_many_arguments)]
async fn bootstrap_acp_connection(
child: &mut AsyncGroupChild,
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 {View on GitHub (pinned to 4deb7eca8f)
Solutions
- Spawn the child with stdout (and stdin) piped — ensure Command::stdout(Stdio::piped()).
- Verify bootstrap_acp_connection is called exactly once per child process.
- Check the executor's spawn code path matches the ACP-enabled configuration (stderr-only piping is not enough).
- Inspect any wrapper/shell script that redirects the agent's stdout away from the pipe.
Example fix
// before let mut cmd = Command::new(agent); cmd.stderr(Stdio::piped()); // after let mut cmd = Command::new(agent); cmd.stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped());
Defensive patterns
Strategy: type-guard
Type guard
fn has_piped_stdio(child: &Child) -> bool {
child.stdout.is_some() && child.stdin.is_some()
}
if !has_piped_stdio(&child) {
anyhow::bail!("respawn agent with piped stdin/stdout before ACP bootstrap");
} Try / catch
match bootstrap_acp_connection(...).await {
Err(ExecutorError::Io(e)) if e.kind() == ErrorKind::NotFound
&& e.to_string().contains("no stdout") => {
// respawn with Stdio::piped()
}
other => other,
} Prevention
- Always spawn ACP agents with stdin/stdout piped.
- Bootstrap the ACP harness exactly once per child.
- Keep spawn configuration in sync with executor ACP settings.
When it happens
Trigger: Calling bootstrap_acp_connection on a child process spawned with stdout not set to piped, or after stdout/stdin were already taken by another bootstrap attempt.
Common situations: Custom spawn configuration disabling piped stdio; double-bootstrapping the same child; spawning the agent with stdout redirected to a file or terminal in a wrapper script; executor config change (e.g. enabling ACP mode for an executor whose spawn code doesn't pipe stdio).
Related errors
- Child process has no stdin
- 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/2780cfc465432b10.
Report an issue: GitHub.