cli: add package path from install context

This commit is contained in:
Michael Bolin
2026-06-03 12:28:58 -07:00
parent 4417e4c193
commit 44920dcd42
11 changed files with 387 additions and 99 deletions

View File

@@ -75,45 +75,25 @@ if (!platformPackage) {
throw new Error(`Unsupported target triple: ${targetTriple}`);
}
const codexBinaryName = process.platform === "win32" ? "codex.exe" : "codex";
const localVendorRoot = path.join(__dirname, "..", "vendor");
const packageBinaryPath = (vendorRoot) =>
path.join(vendorRoot, targetTriple, "bin", codexBinaryName);
const legacyBinaryPath = (vendorRoot) =>
path.join(vendorRoot, targetTriple, "codex", codexBinaryName);
function resolveNativePackage(vendorRoot) {
const packageRoot = path.join(vendorRoot, targetTriple);
const binaryPath = packageBinaryPath(vendorRoot);
if (existsSync(binaryPath)) {
return {
binaryPath,
pathDir: path.join(packageRoot, "codex-path"),
};
function findCodexExecutable() {
let vendorRoot;
try {
const packageJsonPath = require.resolve(`${platformPackage}/package.json`);
vendorRoot = path.join(path.dirname(packageJsonPath), "vendor");
} catch {
vendorRoot = path.join(__dirname, "..", "vendor");
}
const legacyPath = legacyBinaryPath(vendorRoot);
if (existsSync(legacyPath)) {
return {
binaryPath: legacyPath,
pathDir: path.join(packageRoot, "path"),
};
}
return null;
}
let nativePackage;
try {
const packageJsonPath = require.resolve(`${platformPackage}/package.json`);
nativePackage = resolveNativePackage(
path.join(path.dirname(packageJsonPath), "vendor"),
const codexExecutable = path.join(
vendorRoot,
targetTriple,
"bin",
process.platform === "win32" ? "codex.exe" : "codex",
);
} catch {
nativePackage = resolveNativePackage(localVendorRoot);
}
if (existsSync(codexExecutable)) {
return codexExecutable;
}
if (!nativePackage) {
const packageManager = detectPackageManager();
const updateCommand =
packageManager === "bun"
@@ -124,7 +104,7 @@ if (!nativePackage) {
);
}
const { binaryPath, pathDir } = nativePackage;
const binaryPath = findCodexExecutable();
// Use an asynchronous spawn instead of spawnSync so that Node is able to
// respond to signals (e.g. Ctrl-C / SIGINT) while the native binary is
@@ -132,16 +112,6 @@ const { binaryPath, pathDir } = nativePackage;
// and guarantees that when either the child terminates or the parent
// receives a fatal signal, both processes exit in a predictable manner.
function getUpdatedPath(newDirs) {
const pathSep = process.platform === "win32" ? ";" : ":";
const existingPath = process.env.PATH || "";
const updatedPath = [
...newDirs,
...existingPath.split(pathSep).filter(Boolean),
].join(pathSep);
return updatedPath;
}
/**
* Use heuristics to detect the package manager that was used to install Codex
* in order to give the user a hint about how to update it.
@@ -167,19 +137,15 @@ function detectPackageManager() {
return userAgent ? "npm" : null;
}
const additionalDirs = [];
if (existsSync(pathDir)) {
additionalDirs.push(pathDir);
}
const updatedPath = getUpdatedPath(additionalDirs);
const env = { ...process.env, PATH: updatedPath };
const packageManagerEnvVar =
detectPackageManager() === "bun"
? "CODEX_MANAGED_BY_BUN"
: "CODEX_MANAGED_BY_NPM";
env[packageManagerEnvVar] = "1";
env.CODEX_MANAGED_PACKAGE_ROOT = realpathSync(path.join(__dirname, ".."));
const env = {
...process.env,
[packageManagerEnvVar]: "1",
CODEX_MANAGED_PACKAGE_ROOT: realpathSync(path.join(__dirname, "..")),
};
const child = spawn(binaryPath, process.argv.slice(2), {
stdio: "inherit",

2
codex-rs/Cargo.lock generated
View File

@@ -2145,12 +2145,14 @@ dependencies = [
"anyhow",
"codex-apply-patch",
"codex-exec-server",
"codex-install-context",
"codex-linux-sandbox",
"codex-sandboxing",
"codex-shell-escalation",
"codex-utils-absolute-path",
"codex-utils-home-dir",
"dotenvy",
"pretty_assertions",
"tempfile",
"tokio",
]

View File

@@ -16,6 +16,7 @@ workspace = true
anyhow = { workspace = true }
codex-apply-patch = { workspace = true }
codex-exec-server = { workspace = true }
codex-install-context = { workspace = true }
codex-linux-sandbox = { workspace = true }
codex-sandboxing = { workspace = true }
codex-shell-escalation = { workspace = true }
@@ -24,3 +25,6 @@ codex-utils-home-dir = { workspace = true }
dotenvy = { workspace = true }
tempfile = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread"] }
[dev-dependencies]
pretty_assertions = { workspace = true }

View File

@@ -1,3 +1,4 @@
use std::ffi::OsString;
use std::fs::File;
use std::future::Future;
use std::path::Path;
@@ -5,6 +6,7 @@ use std::path::PathBuf;
use codex_apply_patch::CODEX_CORE_APPLY_PATCH_ARG1;
use codex_exec_server::CODEX_FS_HELPER_ARG1;
use codex_install_context::InstallContext;
use codex_sandboxing::landlock::CODEX_LINUX_SANDBOX_ARG0;
use codex_utils_home_dir::find_codex_home;
#[cfg(unix)]
@@ -138,13 +140,30 @@ pub fn arg0_dispatch() -> Option<Arg0PathEntryGuard> {
// before creating any threads/the Tokio runtime.
load_dotenv();
match prepend_path_entry_for_codex_aliases() {
Ok(path_entry) => Some(path_entry),
let (path_entry_guard, updated_path_env_var) = prepare_path_env_var();
if let Some(updated_path_env_var) = updated_path_env_var {
// It is safe to call set_var() because our process is single-threaded at
// this point in its execution.
unsafe {
std::env::set_var("PATH", updated_path_env_var);
}
}
path_entry_guard
}
fn prepare_path_env_var() -> (Option<Arg0PathEntryGuard>, Option<OsString>) {
let existing_path = std::env::var_os("PATH");
let package_path =
path_env_with_package_path_dir(InstallContext::current(), existing_path.clone());
let path_for_aliases = package_path.clone().or(existing_path);
match prepare_path_entry_for_codex_aliases(path_for_aliases) {
Ok((path_entry, updated_path_env_var)) => (Some(path_entry), Some(updated_path_env_var)),
Err(err) => {
// It is possible that Codex will proceed successfully even if
// updating the PATH fails, so warn the user and move on.
eprintln!("WARNING: proceeding, even though we could not update PATH: {err}");
None
// creating helper aliases fails, so warn the user and move on.
eprintln!("WARNING: proceeding, even though we could not create PATH aliases: {err}");
(None, package_path)
}
}
}
@@ -285,15 +304,16 @@ where
/// - WINDOWS: `apply_patch.bat` batch script to invoke the current executable
/// with the hidden `--codex-run-as-apply-patch` flag.
///
/// This temporary directory is prepended to the PATH environment variable so
/// that `apply_patch` can be on the PATH without requiring the user to
/// install a separate `apply_patch` executable, simplifying the deployment of
/// Codex CLI.
/// Returns the temporary directory guard and the PATH value that prepends the
/// temporary directory so `apply_patch` can be on the PATH without requiring the
/// user to install a separate executable, simplifying the deployment of Codex
/// CLI.
/// Note: In debug builds the temp-dir guard is disabled to ease local testing.
///
/// IMPORTANT: This function modifies the PATH environment variable, so it MUST
/// be called before multiple threads are spawned.
pub fn prepend_path_entry_for_codex_aliases() -> std::io::Result<Arg0PathEntryGuard> {
/// IMPORTANT: Callers must update PATH before multiple threads are spawned.
fn prepare_path_entry_for_codex_aliases(
existing_path: Option<OsString>,
) -> std::io::Result<(Arg0PathEntryGuard, OsString)> {
let codex_home = find_codex_home()?;
#[cfg(not(debug_assertions))]
{
@@ -371,27 +391,7 @@ pub fn prepend_path_entry_for_codex_aliases() -> std::io::Result<Arg0PathEntryGu
}
}
#[cfg(unix)]
const PATH_SEPARATOR: &str = ":";
#[cfg(windows)]
const PATH_SEPARATOR: &str = ";";
let updated_path_env_var = match std::env::var_os("PATH") {
Some(existing_path) => {
let mut path_env_var =
std::ffi::OsString::with_capacity(path.as_os_str().len() + 1 + existing_path.len());
path_env_var.push(path);
path_env_var.push(PATH_SEPARATOR);
path_env_var.push(existing_path);
path_env_var
}
None => path.as_os_str().to_owned(),
};
unsafe {
std::env::set_var("PATH", updated_path_env_var);
}
let updated_path_env_var = path_env_with_entry(path, existing_path);
let paths = Arg0DispatchPaths {
codex_self_exe: std::env::current_exe().ok(),
@@ -417,7 +417,41 @@ pub fn prepend_path_entry_for_codex_aliases() -> std::io::Result<Arg0PathEntryGu
},
};
Ok(Arg0PathEntryGuard::new(temp_dir, lock_file, paths))
Ok((
Arg0PathEntryGuard::new(temp_dir, lock_file, paths),
updated_path_env_var,
))
}
fn path_env_with_package_path_dir(
install_context: &InstallContext,
existing_path: Option<OsString>,
) -> Option<OsString> {
let path_dir = install_context
.package_layout
.as_ref()
.and_then(|package_layout| package_layout.path_dir.as_ref())?;
Some(path_env_with_entry(path_dir.as_path(), existing_path))
}
fn path_env_with_entry(path_entry: &Path, existing_path: Option<OsString>) -> OsString {
#[cfg(unix)]
const PATH_SEPARATOR: &str = ":";
#[cfg(windows)]
const PATH_SEPARATOR: &str = ";";
let capacity = path_entry.as_os_str().len()
+ existing_path
.as_ref()
.map_or(0, |existing_path| 1 + existing_path.len());
let mut path_env_var = OsString::with_capacity(capacity);
path_env_var.push(path_entry);
if let Some(existing_path) = existing_path {
path_env_var.push(PATH_SEPARATOR);
path_env_var.push(existing_path);
}
path_env_var
}
fn janitor_cleanup(temp_root: &Path) -> std::io::Result<()> {
@@ -475,6 +509,11 @@ mod tests {
use super::run_main_with_arg0_guard;
#[cfg(unix)]
use anyhow::ensure;
use codex_install_context::CodexPackageLayout;
use codex_install_context::InstallContext;
use codex_install_context::InstallMethod;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
use std::fs;
use std::fs::File;
use std::path::Path;
@@ -513,6 +552,43 @@ mod tests {
Ok(())
}
#[test]
fn path_env_can_prepend_package_path_before_arg0_alias_dir() -> anyhow::Result<()> {
let temp_dir = TempDir::new()?;
let arg0_dir = temp_dir.path().join("arg0");
let package_dir = temp_dir.path().join("package");
let bin_dir = package_dir.join("bin");
let path_dir = package_dir.join("codex-path");
let existing_dir = temp_dir.path().join("existing-bin");
fs::create_dir_all(&arg0_dir)?;
fs::create_dir_all(&bin_dir)?;
fs::create_dir_all(&path_dir)?;
fs::create_dir_all(&existing_dir)?;
let path_dir = AbsolutePathBuf::from_absolute_path(path_dir.canonicalize()?)?;
let install_context = InstallContext {
method: InstallMethod::Other,
package_layout: Some(CodexPackageLayout {
package_dir: AbsolutePathBuf::from_absolute_path(package_dir.canonicalize()?)?,
bin_dir: AbsolutePathBuf::from_absolute_path(bin_dir.canonicalize()?)?,
resources_dir: None,
path_dir: Some(path_dir.clone()),
}),
};
let package_path = super::path_env_with_package_path_dir(
&install_context,
Some(existing_dir.as_os_str().to_owned()),
)
.expect("package path dir should update PATH");
let updated_path = super::path_env_with_entry(&arg0_dir, Some(package_path));
assert_eq!(
std::env::split_paths(&updated_path).collect::<Vec<_>>(),
vec![arg0_dir, path_dir.as_path().to_path_buf(), existing_dir,],
);
Ok(())
}
#[cfg(unix)]
#[test]
fn run_main_with_arg0_guard_keeps_aliases_alive_until_main_returns() -> anyhow::Result<()> {

View File

@@ -1,9 +1,11 @@
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use codex_async_utils::CancelErr;
use codex_async_utils::OrCancelExt;
use codex_network_proxy::PROXY_ACTIVE_ENV_KEY;
use codex_utils_absolute_path::AbsolutePathBuf;
use tokio_util::sync::CancellationToken;
use tracing::error;
use uuid::Uuid;
@@ -17,6 +19,8 @@ use crate::session::TurnInput;
use crate::session::turn_context::TurnContext;
use crate::state::TaskKind;
use crate::tools::format_exec_output_str;
#[cfg(unix)]
use crate::tools::runtimes::apply_package_path_prepend;
use crate::tools::runtimes::maybe_wrap_shell_lc_with_snapshot;
use crate::tools::runtimes::strip_managed_proxy_env;
use crate::turn_timing::now_unix_timestamp_ms;
@@ -131,13 +135,13 @@ pub(crate) async fn execute_user_shell_command(
if exec_env_map.contains_key(PROXY_ACTIVE_ENV_KEY) {
strip_managed_proxy_env(&mut exec_env_map);
}
let exec_command = maybe_wrap_shell_lc_with_snapshot(
let exec_command = prepare_user_shell_exec_command(
&display_command,
session_shell.as_ref(),
#[allow(deprecated)]
&turn_context.cwd,
&turn_context.shell_environment_policy.r#set,
&exec_env_map,
&mut exec_env_map,
);
let call_id = Uuid::new_v4().to_string();
@@ -328,6 +332,57 @@ pub(crate) async fn execute_user_shell_command(
}
}
fn prepare_user_shell_exec_command(
display_command: &[String],
session_shell: &crate::shell::Shell,
cwd: &AbsolutePathBuf,
shell_environment_set: &HashMap<String, String>,
exec_env_map: &mut HashMap<String, String>,
) -> Vec<String> {
#[cfg(unix)]
{
prepare_user_shell_exec_command_with_path_prepend(
display_command,
session_shell,
cwd,
shell_environment_set,
exec_env_map,
apply_package_path_prepend,
)
}
#[cfg(not(unix))]
{
maybe_wrap_shell_lc_with_snapshot(
display_command,
session_shell,
cwd,
shell_environment_set,
exec_env_map,
)
}
}
#[cfg(unix)]
fn prepare_user_shell_exec_command_with_path_prepend(
display_command: &[String],
session_shell: &crate::shell::Shell,
cwd: &AbsolutePathBuf,
shell_environment_set: &HashMap<String, String>,
exec_env_map: &mut HashMap<String, String>,
apply_path_prepend: impl FnOnce(&mut HashMap<String, String>, &mut HashMap<String, String>),
) -> Vec<String> {
let mut explicit_env_overrides = shell_environment_set.clone();
apply_path_prepend(exec_env_map, &mut explicit_env_overrides);
maybe_wrap_shell_lc_with_snapshot(
display_command,
session_shell,
cwd,
&explicit_env_overrides,
exec_env_map,
)
}
async fn persist_user_shell_output(
session: &Session,
turn_context: &TurnContext,
@@ -351,3 +406,7 @@ async fn persist_user_shell_output(
.inject_no_new_turn(vec![output_item], Some(turn_context))
.await;
}
#[cfg(all(test, unix))]
#[path = "user_shell_tests.rs"]
mod tests;

View File

@@ -0,0 +1,72 @@
use super::*;
use crate::shell::Shell;
use crate::shell::ShellType;
use crate::shell_snapshot::ShellSnapshot;
use crate::tools::runtimes::apply_path_prepend;
use core_test_support::PathExt;
use pretty_assertions::assert_eq;
use std::path::PathBuf;
use std::process::Command;
use tokio::sync::watch;
fn shell_with_snapshot(
shell_type: ShellType,
shell_path: &str,
snapshot_path: AbsolutePathBuf,
snapshot_cwd: AbsolutePathBuf,
) -> Shell {
let (_tx, shell_snapshot) = watch::channel(Some(Arc::new(ShellSnapshot {
path: snapshot_path,
cwd: snapshot_cwd,
})));
Shell {
shell_type,
shell_path: PathBuf::from(shell_path),
shell_snapshot,
}
}
#[test]
fn user_shell_snapshot_preserves_package_path_prepend() {
let dir = tempfile::tempdir().expect("create temp dir");
let snapshot_path = dir.path().join("snapshot.sh");
std::fs::write(
&snapshot_path,
"# Snapshot file\nexport PATH='/snapshot/bin'\n",
)
.expect("write snapshot");
let session_shell = shell_with_snapshot(
ShellType::Bash,
"/bin/bash",
snapshot_path.abs(),
dir.path().abs(),
);
let command = vec![
"/bin/bash".to_string(),
"-lc".to_string(),
"printf '%s' \"$PATH\"".to_string(),
];
let package_path_dir = dir.path().join("codex-path");
let mut env = HashMap::from([("PATH".to_string(), "/worktree/bin".to_string())]);
let rewritten = prepare_user_shell_exec_command_with_path_prepend(
&command,
&session_shell,
&dir.path().abs(),
&HashMap::new(),
&mut env,
|env, explicit_env_overrides| {
apply_path_prepend(env, explicit_env_overrides, package_path_dir.as_path());
},
);
let output = Command::new(&rewritten[0])
.args(&rewritten[1..])
.env("PATH", env.get("PATH").expect("PATH should be set"))
.output()
.expect("run rewritten command");
assert!(output.status.success(), "command failed: {output:?}");
assert_eq!(
String::from_utf8_lossy(&output.stdout),
format!("{}:/worktree/bin", package_path_dir.display())
);
}

View File

@@ -10,6 +10,8 @@ use crate::sandboxing::SandboxPermissions;
use crate::shell::Shell;
use crate::shell::ShellType;
use crate::tools::sandboxing::ToolError;
#[cfg(unix)]
use codex_install_context::InstallContext;
#[cfg(target_os = "macos")]
use codex_network_proxy::CODEX_PROXY_GIT_SSH_COMMAND_MARKER;
use codex_network_proxy::CUSTOM_CA_ENV_KEYS;
@@ -99,6 +101,35 @@ fn prepend_path_entry(env: &mut HashMap<String, String>, path_entry: &str) -> St
updated_path
}
#[cfg(unix)]
pub(crate) fn apply_package_path_prepend(
env: &mut HashMap<String, String>,
explicit_env_overrides: &mut HashMap<String, String>,
) {
let Some(path_dir) = InstallContext::current()
.package_layout
.as_ref()
.and_then(|package_layout| package_layout.path_dir.as_ref())
else {
return;
};
apply_path_prepend(env, explicit_env_overrides, path_dir.as_path());
}
#[cfg(unix)]
pub(crate) fn apply_path_prepend(
env: &mut HashMap<String, String>,
explicit_env_overrides: &mut HashMap<String, String>,
path_entry: &Path,
) {
let path_entry = path_entry.to_string_lossy();
let updated_path = prepend_path_entry(env, path_entry.as_ref());
// Snapshot wrapping restores explicit overrides after sourcing the shell
// snapshot, so capture this PATH override there as well.
explicit_env_overrides.insert("PATH".to_string(), updated_path);
}
#[cfg(unix)]
pub(crate) fn prepend_zsh_fork_bin_to_path(
env: &mut HashMap<String, String>,
@@ -116,12 +147,10 @@ pub(crate) fn apply_zsh_fork_path_prepend(
explicit_env_overrides: &mut HashMap<String, String>,
shell_zsh_path: &Path,
) {
let Some(updated_path) = prepend_zsh_fork_bin_to_path(env, shell_zsh_path) else {
let Some(zsh_bin_dir) = shell_zsh_path.parent() else {
return;
};
// Snapshot wrapping restores explicit overrides after sourcing the shell
// snapshot, so capture this PATH override there as well.
explicit_env_overrides.insert("PATH".to_string(), updated_path);
apply_path_prepend(env, explicit_env_overrides, zsh_bin_dir);
}
pub(crate) fn disable_powershell_profile_for_elevated_windows_sandbox(

View File

@@ -166,6 +166,26 @@ fn explicit_escalation_preserves_user_ca_env() {
);
}
#[cfg(unix)]
#[test]
fn apply_path_prepend_records_explicit_path_override() {
let mut env = HashMap::from([("PATH".to_string(), "/usr/bin:/bin".to_string())]);
let mut explicit_env_overrides = HashMap::new();
apply_path_prepend(
&mut env,
&mut explicit_env_overrides,
PathBuf::from("/package/codex-path").as_path(),
);
let expected = "/package/codex-path:/usr/bin:/bin";
assert_eq!(env.get("PATH").map(String::as_str), Some(expected));
assert_eq!(
explicit_env_overrides.get("PATH").map(String::as_str),
Some(expected)
);
}
#[cfg(unix)]
#[test]
fn apply_zsh_fork_path_prepend_uses_shell_parent() {
@@ -883,6 +903,55 @@ fn maybe_wrap_shell_lc_with_snapshot_applies_explicit_path_override() {
assert_eq!(String::from_utf8_lossy(&output.stdout), "/worktree/bin");
}
#[cfg(unix)]
#[test]
fn maybe_wrap_shell_lc_with_snapshot_preserves_package_path_prepend() {
let dir = tempdir().expect("create temp dir");
let snapshot_path = dir.path().join("snapshot.sh");
std::fs::write(
&snapshot_path,
"# Snapshot file\nexport PATH='/snapshot/bin'\n",
)
.expect("write snapshot");
let session_shell = shell_with_snapshot(
ShellType::Bash,
"/bin/bash",
snapshot_path.abs(),
dir.path().abs(),
);
let command = vec![
"/bin/bash".to_string(),
"-lc".to_string(),
"printf '%s' \"$PATH\"".to_string(),
];
let package_path_dir = dir.path().join("codex-path");
let mut env = HashMap::from([("PATH".to_string(), "/worktree/bin".to_string())]);
let mut explicit_env_overrides = HashMap::new();
apply_path_prepend(
&mut env,
&mut explicit_env_overrides,
package_path_dir.as_path(),
);
let rewritten = maybe_wrap_shell_lc_with_snapshot(
&command,
&session_shell,
&dir.path().abs(),
&explicit_env_overrides,
&env,
);
let output = Command::new(&rewritten[0])
.args(&rewritten[1..])
.env("PATH", env.get("PATH").expect("PATH should be set"))
.output()
.expect("run rewritten command");
assert!(output.status.success(), "command failed: {output:?}");
assert_eq!(
String::from_utf8_lossy(&output.stdout),
format!("{}:/worktree/bin", package_path_dir.display())
);
}
#[cfg(unix)]
#[test]
fn maybe_wrap_shell_lc_with_snapshot_preserves_zsh_fork_path_prepend() {

View File

@@ -21,6 +21,8 @@ use crate::tools::flat_tool_name;
use crate::tools::network_approval::NetworkApprovalMode;
use crate::tools::network_approval::NetworkApprovalSpec;
#[cfg(unix)]
use crate::tools::runtimes::apply_package_path_prepend;
#[cfg(unix)]
use crate::tools::runtimes::apply_zsh_fork_path_prepend;
use crate::tools::runtimes::build_sandbox_command;
use crate::tools::runtimes::disable_powershell_profile_for_elevated_windows_sandbox;
@@ -252,6 +254,7 @@ impl ToolRuntime<ShellRequest, ExecToolCallOutput> for ShellRuntime {
let (env, explicit_env_overrides) = {
let mut env = env;
let mut explicit_env_overrides = explicit_env_overrides;
apply_package_path_prepend(&mut env, &mut explicit_env_overrides);
if self.backend == ShellRuntimeBackend::ShellCommandZshFork
&& let Some(shell_zsh_path) = ctx.session.services.shell_zsh_path.as_deref()
{

View File

@@ -18,6 +18,8 @@ use crate::tools::flat_tool_name;
use crate::tools::network_approval::NetworkApprovalMode;
use crate::tools::network_approval::NetworkApprovalSpec;
#[cfg(unix)]
use crate::tools::runtimes::apply_package_path_prepend;
#[cfg(unix)]
use crate::tools::runtimes::apply_zsh_fork_path_prepend;
use crate::tools::runtimes::build_sandbox_command;
use crate::tools::runtimes::disable_powershell_profile_for_elevated_windows_sandbox;
@@ -278,10 +280,14 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
if let Some(network) = managed_network {
network.apply_to_env(&mut env);
}
let environment_is_remote = req.environment.is_remote();
let explicit_env_overrides = req.explicit_env_overrides.clone();
#[cfg(unix)]
let explicit_env_overrides = {
let mut explicit_env_overrides = explicit_env_overrides;
if !environment_is_remote {
apply_package_path_prepend(&mut env, &mut explicit_env_overrides);
}
if let UnifiedExecShellMode::ZshFork(zsh_fork_config) = &self.shell_mode {
apply_zsh_fork_path_prepend(
&mut env,
@@ -291,7 +297,6 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
}
explicit_env_overrides
};
let environment_is_remote = req.environment.is_remote();
let command = if environment_is_remote {
base_command.to_vec()
} else {

View File

@@ -184,11 +184,14 @@ impl InstallContext {
impl CodexPackageLayout {
fn from_exe(exe_path: &Path) -> Option<Self> {
let canonical_exe = canonical_absolute_path(exe_path)?;
let bin_dir = canonical_exe.parent()?;
if bin_dir.file_name() != Some(OsStr::new(BIN_DIRNAME)) {
return None;
let exe_dir = canonical_exe.parent()?;
match exe_dir.file_name() {
Some(name) if name == OsStr::new(BIN_DIRNAME) => Self::from_package_bin_dir(exe_dir),
Some(_) | None => None,
}
}
fn from_package_bin_dir(bin_dir: AbsolutePathBuf) -> Option<Self> {
let package_dir = bin_dir.parent()?;
if !package_dir.join(PACKAGE_METADATA_FILENAME).is_file() {
return None;