Hmbown/CodeWhale · error · anyhow::Error
MCP stdio spawn failed
Error message
MCP stdio spawn failed (transport=stdio server={server_name} cmd={command:?} args={:?} env_keys={env_keys:?}) What it means
When spawning an MCP stdio server process fails, spawn builds a diagnostic message embedding the transport, server name, command, args, and only the env variable NAMES (never values) before attaching the original error as context. This gives enough detail to debug the launch without leaking secrets.
Solutions
- Run the configured command and args by hand in a shell to see the OS-level error.
- Install the server binary or fix its absolute path in the MCP server config.
- Verify required env vars referenced by the config are set (only their keys appear in the message).
- Check execute permissions on the command file.
Example fix
// before: config "command": "my-mcp-server" // after: install it or use an absolute path "command": "/usr/local/bin/my-mcp-server"
Defensive patterns
Strategy: validation
Validate before calling
let which = which(&command);
if which.is_none() { eprintln!("command not found: {command}"); }
// also verify execute bit: metadata.permissions().mode() & 0o111 != 0 Try / catch
match spawn().await {
Err(e) if e.to_string().contains("MCP stdio spawn failed") => {
eprintln!("{e:#}; install the server binary or fix the configured path");
}
other => other?,
} Prevention
- Install MCP server binaries before configuring them, and verify with `command -v`.
- Prefer absolute paths or pinned versions (npx pkg@version) in server configs.
- Keep required env vars set in the launch environment (only names are logged, never values).
When it happens
Trigger: Child-process spawn fails for an MCP server configured with transport=stdio: the command binary does not exist, is not executable, the working directory is missing, or env expansion yields an unusable command.
Common situations: Server command not installed or not on PATH (e.g. npx/uvx missing); wrong path in config; missing execute bit; typo in command name; interpreter version too old.
Related errors
- 125
- child returned a malformed MCP CallToolResult
- child stdout was not valid UTF-8
- connection stdin poisoned by an earlier panic
- failed to read bounded stdio JSON-RPC frame
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/81e589ab1e51ed41.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/mcp/stdio.rs:184
let message = if error.kind() == std::io::ErrorKind::NotFound
&& super::is_node_command(command)
&& launch_cwd.is_none_or(|directory| directory.is_dir())
{
format!("MCP server {server_name} could not start because Node.js was not found. Install Node.js from https://nodejs.org/ and restart Codewhale with node on PATH. Built-in Computer Use requires Node.js 20 or newer.")
} else if config.reviewed_plugin.is_some() {
format!(
"MCP stdio spawn failed (transport=stdio server={server_name} reviewed-plugin argv_count={} env_count={})",
config.args.len(),
expanded_env.len(),
)
} else {
let env_keys: Vec<&str> = expanded_env.keys().map(String::as_str).collect();
format!(
"MCP stdio spawn failed (transport=stdio server={server_name} cmd={command:?} args={:?} env_keys={env_keys:?})",
config.args,
)
};
anyhow::Error::new(error).context(message)
})?;
let stdin = child.stdin.take().context("Failed to get MCP stdin")?;
let stdout = child.stdout.take().context("Failed to get MCP stdout")?;
let stderr = child.stderr.take().context("Failed to get MCP stderr")?;
// Drain stderr into a bounded ring buffer so a crash mid-run leaves
// diagnostic breadcrumbs instead of disappearing into `Stdio::null`.
// The task exits naturally when the child closes its stderr
// (kill_on_drop / exit / explicit shutdown).
let stderr_tail = StderrTail::new();
{
let tail = Arc::clone(&stderr_tail);
// A reviewed plugin child receives environment-backed values that
// are intentionally absent from its manifest. Still drain its
// stderr to avoid blocking, but do not retain or surface arbitrary
// child output that could echo those credentials into a chat or
// persisted transcript.View on GitHub (pinned to 73e0f67d83)