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.

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.

Appendix: 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)