Support packaged managed Codex binary paths (#42318)

## What changed

- Prefer the managed executable under `packages/standalone/current/bin` when it exists.
- Fall back to the legacy `packages/standalone/current` executable layout.
- Preserve platform-specific missing-install paths so launch validation and lifecycle commands continue to behave as expected.

## Testing

- Add a path-selection test covering missing, legacy, and packaged installs.

GitOrigin-RevId: 4c0d066257e9984a35e3b95dd83c768d9ce69431
This commit is contained in:
Eric Traut
2026-09-02 17:52:16 +00:00
committed by copyberry
parent f252c23b88
commit a94a5db629
2 changed files with 41 additions and 3 deletions
@@ -1,3 +1,5 @@
//! Resolves packaged and legacy standalone install layouts.
use std::path::Path;
use std::path::PathBuf;
@@ -16,12 +18,23 @@ use tokio::fs;
#[cfg(unix)]
use tokio::process::Command;
/// Returns the packaged executable when present, otherwise an existing legacy executable.
/// If neither exists, returns the expected packaged path on Windows and preserves the
/// historical legacy fallback on Unix. This is path selection, not existence validation:
/// launch operations reject a missing executable separately, while commands such as
/// stop can still run after the managed install has been removed.
pub(crate) fn managed_codex_bin(codex_home: &Path) -> PathBuf {
codex_home
let current = codex_home
.join("packages")
.join("standalone")
.join("current")
.join(managed_codex_file_name())
.join("current");
let packaged = current.join("bin").join(managed_codex_file_name());
let legacy = current.join(managed_codex_file_name());
if packaged.is_file() || (cfg!(windows) && !legacy.is_file()) {
packaged
} else {
legacy
}
}
#[cfg(unix)]
@@ -101,3 +114,7 @@ fn parse_codex_version(output: &str) -> Result<String> {
#[cfg(all(test, unix))]
#[path = "managed_install_tests.rs"]
mod tests;
#[cfg(test)]
#[path = "managed_install_path_tests.rs"]
mod path_tests;
@@ -0,0 +1,21 @@
use pretty_assertions::assert_eq;
#[test]
fn discovers_package_and_legacy_installs() {
let home = tempfile::TempDir::new().expect("home");
let current = home.path().join("packages/standalone/current");
let legacy = current.join(super::managed_codex_file_name());
let expected = if cfg!(windows) {
current.join("bin").join("codex.exe")
} else {
legacy.clone()
};
assert_eq!(super::managed_codex_bin(home.path()), expected);
std::fs::create_dir_all(&current).expect("current directory");
std::fs::write(&legacy, b"legacy").expect("legacy executable");
assert_eq!(super::managed_codex_bin(home.path()), legacy);
let packaged = current.join("bin").join(super::managed_codex_file_name());
std::fs::create_dir(current.join("bin")).expect("bin directory");
std::fs::write(&packaged, b"packaged").expect("packaged executable");
assert_eq!(super::managed_codex_bin(home.path()), packaged);
}