BigPizzaV3/CodexPlusPlus · error · anyhow::Error

Codex command is empty

Error message

Codex command is empty

What it means

In the non-.app launch branch (Windows/Linux or direct executable), the launcher builds the Codex argv via build_codex_command / build_codex_command_with_native_menu_inspector and requires a first element (the executable path from app_paths::build_codex_executable) before spawning (crates/codex-plus-core/src/launcher.rs:814-816). Both builders unconditionally start with the executable string, so this error is a defensive invariant against a modified/empty builder, not an expected runtime outcome.

Source

Thrown at crates/codex-plus-core/src/launcher.rs:816

                command,
                wait_strategy: ProcessWaitStrategy::ExternalWaitCommand,
                macos_cleanup_policy: Some(cleanup_policy),
            });
        }

        let command = if let Some(inspector_port) = native_menu_inspector_port {
            build_codex_command_with_native_menu_inspector(
                app_dir,
                debug_port,
                inspector_port,
                &launch_extra_args,
            )
        } else {
            build_codex_command(app_dir, debug_port, &launch_extra_args)
        };
        let executable = command
            .first()
            .ok_or_else(|| anyhow::anyhow!("Codex command is empty"))?;
        let mut child_command = Command::new(executable);
        child_command
            .args(&command[1..])
            .stdout(Stdio::null())
            .stderr(Stdio::null());
        #[cfg(windows)]
        child_command.creation_flags(crate::windows_integration::CREATE_NO_WINDOW);
        let child = child_command
            .spawn()
            .with_context(|| format!("failed to launch Codex executable {executable}"))?;
        *self.child.lock().await = Some(child);
        if let Some(inspector_port) = native_menu_inspector_port {
            start_native_menu_localizer(inspector_port);
        }
        Ok(CodexLaunch::Process {
            command,
            wait_strategy: ProcessWaitStrategy::TrackedChild,
            macos_cleanup_policy: None,

View on GitHub (pinned to 1f431ae49b)

Solutions

  1. Restore the unconditional executable first element in any forked build_codex_command* so the vec is never empty
  2. Validate app_dir resolution before launching: ensure resolve_app_dir returned a real directory containing the platform binary
  3. Treat as an assertion: log the full launch inputs and report upstream if it fires in an unmodified build

Example fix

// before (test stub that trips the guard)
let command: Vec<String> = Vec::new();
let executable = command.first().ok_or_else(|| anyhow!("Codex command is empty"))?;

// after
let mut command = vec![crate::app_paths::build_codex_executable(app_dir).to_string_lossy().to_string()];
command.extend(build_codex_arguments(debug_port, extra_args));
let executable = &command[0];
Defensive patterns

Strategy: validation

Validate before calling

let command = build_codex_command(app_dir, debug_port, &launch_extra_args);
if command.first().map(String::is_empty).unwrap_or(true) {
    anyhow::bail!("refusing to spawn: codex command empty for {}", app_dir.display());
}

Type guard

fn spawnable_command(cmd: &[String]) -> Option<&str> {
    cmd.first().map(|s| s.as_str()).filter(|s| !s.trim().is_empty())
}

Try / catch

// Invariant failure: report inputs, do not retry
match launcher.launch().await {
    Err(e) if e.to_string() == "Codex command is empty" => {
        tracing::error!(?app_dir, %debug_port, "command builder regression");
        return Err(e.context("command builder returned empty argv"));
    }
    rest => rest,
}

Prevention

When it happens

Trigger: Launching with an app_dir that is not a .app bundle while a customized build_codex_command* variant returns an empty Vec — e.g. fork code that builds args conditionally, or a stubbed builder in tests that returns Vec::new().

Common situations: Forks that rewrite command assembly; unit tests mocking command builders with empty returns; regressions after changing build_codex_executable to return an empty PathBuf that then formats to an empty-but-present string (note: that yields an empty executable string, not this error — this error needs a literally empty Vec).

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@1f431ae49b (2026-08-16). Data as JSON: /api/errors/c02f63b065e5e91a. Report an issue: GitHub.