BigPizzaV3/CodexPlusPlus · error

无法启动 Codex app-server(

Error message

无法启动 Codex app-server({executable}){hint}({error})

What it means

Thrown when spawning the Codex app-server child process fails. The library wraps std::process::Command spawn errors and appends a hint depending on the io::ErrorKind: NotFound means the executable path does not exist (or is not on PATH), PermissionDenied means the file is not executable. The underlying OS error is preserved in {error}.

Solutions

  1. Set the full absolute path to the Codex CLI executable in settings (e.g. /usr/local/bin/codex or ~/.cargo/bin/codex)
  2. Verify the binary exists and is executable: ls -l $(which codex) / chmod +x
  3. Ensure PATH includes the install dir when launching from a GUI app (GUI apps inherit launchd PATH, not shell PATH)
  4. Reinstall or update the Codex CLI if the binary is missing after an upgrade

Example fix

// before (settings)
codexPath: "codex"
// after
codexPath: "/usr/local/bin/codex"
Defensive patterns

Strategy: try-catch

Validate before calling

let p = settings.codex_path.clone();
if p.is_empty() || !std::path::Path::new(&p).exists() {
    return Err(format!("Codex CLI not found at '{p}'; set the full executable path"));
}
#[cfg(unix)]
{
    use std::os::unix::fs::PermissionsExt;
    let mode = std::fs::metadata(&p)?.permissions().mode();
    if mode & 0o111 == 0 { return Err("Codex CLI is not executable".into()); }
}

Type guard

fn is_executable_file(path: &str) -> bool {
    let p = std::path::Path::new(path);
    p.is_file() && std::fs::metadata(p)
        .map(|m| !m.permissions().readonly())
        .unwrap_or(false)
}

Try / catch

match app_server::start(config).await {
    Err(e) if e.to_string().contains("无法启动 Codex app-server") => {
        eprintln!("Check the codex executable path and permissions: {e}");
    }
    Err(e) => return Err(e),
    Ok(v) => v,
}

Prevention

When it happens

Trigger: Calling start (via start_helper_waiting_for_busy_port) with a configured Codex CLI path that does not exist, is not on PATH, or lacks the execute bit; also any other spawn failure (e.g. resource limits, E2BIG).

Common situations: User left the codex executable path empty or mistyped after upgrading; codex installed via npm/cargo not on the GUI app's PATH; binary downloaded without +x; antivirus or sandbox blocking exec.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19). Data as JSON: /api/errors/b081810c4cd7c3a0. Report an issue: GitHub.

Appendix: source

Thrown at crates/codex-plus-core/src/connect/app_server.rs:118

        };
        validate_codex_executable(executable)?;
        let mut command = Command::new(executable);
        command
            .arg("app-server")
            .current_dir(&config.work_dir)
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .kill_on_drop(true);
        let mut child = command.spawn().map_err(|error| {
            let hint = match error.kind() {
                std::io::ErrorKind::NotFound => {
                    ":找不到该文件;填 Codex CLI 可执行文件的完整路径,或确保 codex 在 PATH 里"
                }
                std::io::ErrorKind::PermissionDenied => ":没有执行权限",
                _ => "",
            };
            anyhow::anyhow!("无法启动 Codex app-server({executable}){hint}({error})")
        })?;
        let stdin = child
            .stdin
            .take()
            .context("无法连接 Codex app-server stdin")?;
        let stdout = child
            .stdout
            .take()
            .context("无法连接 Codex app-server stdout")?;
        // 别把 stderr 读了就扔——app-server 起不来时,真正的原因只在这里。
        // 留最近几行,初始化失败时附到错误信息上。
        let stderr_tail = Arc::new(Mutex::new(VecDeque::<String>::new()));
        if let Some(stderr) = child.stderr.take() {
            let sink = Arc::clone(&stderr_tail);
            tokio::spawn(async move {
                let mut lines = BufReader::new(stderr).lines();
                while let Ok(Some(line)) = lines.next_line().await {
                    let line = line.trim().to_string();

View on GitHub (pinned to b1ed92e5e4)