aaif-goose/goose · critical · anyhow::Error

{e}

Error message

{e}

What it means

The error body is the raw message from spawn_acp_process ('failed to spawn ACP process' plus OS detail), forwarded over the init channel so the ACP provider's initialize future fails instead of hanging. spawn_acp_process builds the child command from config.command/config.args with piped stdio, prepends the command's parent dir to PATH (for npm adapters), applies env_remove/env, then calls cmd.spawn() — so this error means the process itself never started.

Source

Thrown at crates/goose/src/acp/provider.rs:886

    ) -> Self {
        Self {
            config,
            goose_mode,
            prompt_response_tx: Arc::new(Mutex::new(None)),
            pending_tool_updates,
            context_size,
        }
    }

    async fn spawn(
        self,
        mut rx: mpsc::Receiver<ClientRequest>,
        init_tx: oneshot::Sender<Result<InitializeResponse>>,
    ) {
        let child = match spawn_acp_process(&self.config).await {
            Ok(c) => c,
            Err(e) => {
                let _ = init_tx.send(Err(anyhow::anyhow!("{e}")));
                tracing::error!("failed to spawn ACP process: {e}");
                return;
            }
        };

        match self.run_with_child(child, &mut rx, init_tx).await {
            Ok(()) => tracing::debug!("ACP protocol loop exited cleanly"),
            Err(e) => tracing::error!(error = %e, "ACP protocol loop error"),
        }
    }

    async fn run_with_child(
        self,
        mut child: Child,
        rx: &mut mpsc::Receiver<ClientRequest>,
        init_tx: oneshot::Sender<Result<InitializeResponse>>,
    ) -> Result<()> {
        let stdin = child.stdin.take().context("no stdin")?;

View on GitHub (pinned to 3810898a74)

Solutions

  1. Verify the binary resolves and runs in the same context: 'which <command>' and execute it manually with the configured args
  2. Use an absolute path for config.command (e.g. /usr/local/bin/my-agent or the real path under node_modules/.bin)
  3. Ensure the file is executable and, for scripts, has a valid shebang (chmod +x, #!/usr/bin/env node)
  4. Install the missing agent package or point config.command at the correct installed location

Example fix

# before
command: my-acp-agent
args: []

# after
command: /home/user/.npm-global/bin/my-acp-agent
args: []
Defensive patterns

Strategy: validation

Validate before calling

use std::path::Path;
use which::which; // or manual PATH scan

fn agent_command_available(command: &str) -> bool {
    command.contains('/') && Path::new(command).is_file()
        || which(command).is_ok()
}

assert!(
    agent_command_available(&config.command),
    "ACP agent command '{}' not found; install it or use an absolute path",
    config.command
);

Try / catch

match provider.initialize().await {
    Ok(_) => {}
    Err(e) if e.to_string().contains("failed to spawn ACP process") => {
        eprintln!("agent binary missing or not executable: {e}");
        std::process::exit(127); // command-not-found convention, no retry
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: config.command points to a binary that does not exist (ENOENT), is not executable (EACCES), is a directory, or PATH lookup fails because the command lives in a directory absent from PATH and has no explicit parent dir (e.g. bare 'node' resolved from a GUI environment whose PATH omits it).

Common situations: Using an ACP agent installed via npx/pnpm where the bin dir is not on the desktop app's PATH, a typo in the command path, a script missing its shebang or execute bit, or running on a machine where the agent was never installed.

Related errors


AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16). Data as JSON: /api/errors/3b72647a4c563cb4. Report an issue: GitHub.