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

  1. Spawn the child with stdout (and stdin) piped — ensure Command::stdout(Stdio::piped()).
  2. Verify bootstrap_acp_connection is called exactly once per child process.
  3. Check the executor's spawn code path matches the ACP-enabled configuration (stderr-only piping is not enough).
  4. 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

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


AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29). Data as JSON: /api/errors/2780cfc465432b10. Report an issue: GitHub.