Fix Windows sandbox helper lookup for standalone installs

This commit is contained in:
David Wiesen
2026-07-01 12:55:23 -07:00
parent d059658ad1
commit a577d230da
3 changed files with 128 additions and 19 deletions
+1
View File
@@ -28,6 +28,7 @@ chrono = { version = "0.4.42", default-features = false, features = [
"clock",
"std",
] }
codex-install-context = { workspace = true }
codex-utils-pty = { workspace = true }
codex-utils-absolute-path = { workspace = true }
codex-utils-string = { workspace = true }
@@ -1,6 +1,7 @@
use anyhow::Context;
use anyhow::Result;
use anyhow::anyhow;
use codex_install_context::InstallContext;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::fs;
@@ -51,9 +52,7 @@ pub(crate) fn helper_bin_dir(codex_home: &Path) -> PathBuf {
}
pub(crate) fn legacy_lookup(kind: HelperExecutable) -> PathBuf {
if let Ok(exe) = std::env::current_exe()
&& let Some(candidate) = bundled_executable_path_for_exe(&exe, kind.file_name())
{
if let Some(candidate) = bundled_executable_path(kind.file_name()) {
return candidate;
}
PathBuf::from(kind.file_name())
@@ -183,32 +182,69 @@ fn store_helper_path(cache_key: String, path: PathBuf) {
fn sibling_source_path(kind: HelperExecutable) -> Result<PathBuf> {
let exe = std::env::current_exe().context("resolve current executable for helper lookup")?;
bundled_executable_path_for_exe(&exe, kind.file_name()).ok_or_else(|| {
bundled_executable_path(kind.file_name()).ok_or_else(|| {
anyhow!(
"helper not found next to current executable or under {RESOURCES_DIRNAME}: {}",
"helper not found next to current executable, canonical executable, or under {RESOURCES_DIRNAME}: {}",
exe.display()
)
})
}
pub(crate) fn bundled_executable_path_for_exe(exe: &Path, file_name: &str) -> Option<PathBuf> {
let dir = exe.parent()?;
let direct_candidate = dir.join(file_name);
if direct_candidate.is_file() {
return Some(direct_candidate);
pub(crate) fn bundled_executable_path(file_name: &str) -> Option<PathBuf> {
if let Some(resource) = InstallContext::current().bundled_resource(file_name) {
return Some(resource.into_path_buf());
}
let exe = std::env::current_exe().ok()?;
bundled_executable_path_for_exe(&exe, file_name)
}
pub(crate) fn bundled_executable_path_for_exe(exe: &Path, file_name: &str) -> Option<PathBuf> {
bundled_executable_candidates_for_exe(exe, file_name)
.into_iter()
.find(|candidate| candidate.is_file())
}
fn bundled_executable_candidates_for_exe(exe: &Path, file_name: &str) -> Vec<PathBuf> {
let mut candidates = Vec::new();
append_bundled_executable_candidates(&mut candidates, exe, file_name);
if let Ok(canonical_exe) = fs::canonicalize(exe)
&& canonical_exe != exe
{
append_bundled_executable_candidates(&mut candidates, &canonical_exe, file_name);
}
candidates
}
fn append_bundled_executable_candidates(
candidates: &mut Vec<PathBuf>,
exe: &Path,
file_name: &str,
) {
let Some(dir) = exe.parent() else {
return;
};
push_candidate(candidates, dir.join(file_name));
if dir.file_name() == Some(OsStr::new(BIN_DIRNAME))
&& let Some(package_dir) = dir.parent()
{
let package_resource_candidate = package_dir.join(RESOURCES_DIRNAME).join(file_name);
if package_resource_candidate.is_file() {
return Some(package_resource_candidate);
}
push_candidate(
candidates,
package_dir.join(RESOURCES_DIRNAME).join(file_name),
);
}
let resource_candidate = dir.join(RESOURCES_DIRNAME).join(file_name);
resource_candidate.is_file().then_some(resource_candidate)
push_candidate(candidates, dir.join(RESOURCES_DIRNAME).join(file_name));
}
fn push_candidate(candidates: &mut Vec<PathBuf>, candidate: PathBuf) {
if !candidates.iter().any(|existing| existing == &candidate) {
candidates.push(candidate);
}
}
fn helper_destination_for_source(
@@ -373,11 +409,38 @@ mod tests {
use super::helper_version_suffix;
use super::materialized_file_name;
use pretty_assertions::assert_eq;
use std::ffi::OsString;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
use tempfile::TempDir;
struct EnvVarGuard {
key: &'static str,
original: Option<OsString>,
}
impl EnvVarGuard {
fn set(key: &'static str, value: &std::ffi::OsStr) -> Self {
let original = std::env::var_os(key);
unsafe {
std::env::set_var(key, value);
}
Self { key, original }
}
}
impl Drop for EnvVarGuard {
fn drop(&mut self) {
unsafe {
match &self.original {
Some(value) => std::env::set_var(self.key, value),
None => std::env::remove_var(self.key),
}
}
}
}
#[test]
fn copy_from_source_if_needed_copies_missing_destination() {
let tmp = TempDir::new().expect("tempdir");
@@ -543,6 +606,42 @@ mod tests {
assert_eq!(resolved, sibling_helper);
}
#[test]
fn helper_source_lookup_follows_canonicalized_launcher_path() {
let tmp = TempDir::new().expect("tempdir");
let codex_home = tmp.path().join("codex-home");
let release_dir = codex_home
.join("packages")
.join("standalone")
.join("releases")
.join("0.142.5-x86_64-pc-windows-msvc");
let resources_dir = release_dir.join(RESOURCES_DIRNAME);
let bin_dir = release_dir.join(BIN_DIRNAME);
let launcher_dir = tmp
.path()
.join("AppData")
.join("Local")
.join("Programs")
.join("OpenAI")
.join("Codex")
.join(BIN_DIRNAME);
fs::create_dir_all(&resources_dir).expect("create resources dir");
fs::create_dir_all(&launcher_dir).expect("create launcher dir");
fs::create_dir_all(&bin_dir).expect("create package bin dir");
let canonical_exe = bin_dir.join("codex.exe");
let launcher_exe = launcher_dir.join("codex.exe");
let helper = resources_dir.join("codex-command-runner.exe");
fs::write(&canonical_exe, b"codex").expect("write canonical exe");
fs::write(&helper, b"runner").expect("write helper");
symlink_file(&canonical_exe, &launcher_exe);
let _codex_home_guard = EnvVarGuard::set("CODEX_HOME", codex_home.as_os_str());
let resolved = bundled_executable_path_for_exe(&launcher_exe, "codex-command-runner.exe")
.expect("helper path");
assert_eq!(resolved, helper);
}
#[test]
fn helper_version_suffix_uses_cli_version_or_dev_build_metadata() {
let tmp = TempDir::new().expect("tempdir");
@@ -563,4 +662,14 @@ mod tests {
assert_eq!(file_name, "codex-command-runner-test-suffix.exe");
}
#[cfg(unix)]
fn symlink_file(original: &Path, link: &Path) {
std::os::unix::fs::symlink(original, link).expect("create symlink");
}
#[cfg(windows)]
fn symlink_file(original: &Path, link: &Path) {
std::os::windows::fs::symlink_file(original, link).expect("create symlink");
}
}
+2 -3
View File
@@ -12,6 +12,7 @@ use std::process::Stdio;
use crate::allow::AllowDenyPaths;
use crate::allow::compute_allow_paths_for_permissions;
use crate::helper_materialization::bundled_executable_path;
use crate::helper_materialization::bundled_executable_path_for_exe;
use crate::helper_materialization::helper_bin_dir;
use crate::identity::sandbox_setup_is_complete;
@@ -684,9 +685,7 @@ fn quote_arg(arg: &str) -> String {
}
fn find_setup_exe() -> PathBuf {
if let Ok(exe) = std::env::current_exe()
&& let Some(setup_exe) = find_setup_exe_for_current_exe(&exe)
{
if let Some(setup_exe) = bundled_executable_path(SETUP_EXE_FILENAME) {
return setup_exe;
}
PathBuf::from(SETUP_EXE_FILENAME)