cli: add package path from install context (#26189)

## Why

Codex package installs include helper binaries in `codex-path`, such as
the bundled `rg`. Package-layout launches should add that directory
before user commands run, but standalone launches were missing it while
npm launches only worked because `codex.js` had its own legacy `PATH`
rewrite. That made npm and standalone package behavior diverge.

Shell snapshot restoration can also reset `PATH` after runtime setup.
Any package-owned `PATH` prepend has to be recorded as an explicit
runtime override so shells, unified exec, and user-shell commands keep
access to `codex-path` after a snapshot is sourced.

## Repro

Before this change, a curl-installed package could contain `rg` under
`codex-path` but still fail to put it on `PATH`:

```shell
mkdir /tmp/test-codex-curl
curl -fsSL https://chatgpt.com/codex/install.sh \
  | CODEX_HOME=/tmp/test-codex-curl CODEX_NON_INTERACTIVE=1 sh
/tmp/test-codex-curl/packages/standalone/current/bin/codex exec \
  --skip-git-repo-check 'print `which -a rg`'
find /tmp/test-codex-curl -name rg
```

The `which -a rg` output omitted the packaged helper even though `find`
showed it under
`/tmp/test-codex-curl/packages/standalone/releases/.../codex-path/rg`.

The npm install path behaved differently only because
`codex-cli/bin/codex.js` had legacy `PATH` rewriting:

```shell
mkdir /tmp/test-codex-npm
cd /tmp/test-codex-npm
npm install @openai/codex
./node_modules/.bin/codex exec --skip-git-repo-check 'print `which -a rg`'
```

That printed the npm package's `vendor/<target>/codex-path/rg` first.
This PR moves that behavior into Rust-side package launch setup so
curl/standalone and npm/bun launches agree without JS rewriting `PATH`.

## What Changed

- `codex-rs/arg0` now uses
`InstallContext::current().package_layout.path_dir` to prepend the
package helper directory before any threads are created.
- Package helper `PATH` setup is independent from the temporary arg0
alias setup, so `codex-path` is still added even if CODEX_HOME tempdir,
lock, or symlink setup fails.
- `codex-rs/install-context` detects the canonical package layout we
ship: `bin/`, `codex-resources/`, and `codex-path/` next to
`codex-package.json`.
- Shell, local unified exec, and user-shell runtimes now record package
`codex-path` prepends in `explicit_env_overrides`, matching the existing
zsh-fork behavior so shell snapshots cannot restore over the package
helper path.
- Remote unified exec requests do not receive the local app-server
package path overlay.
- `codex-cli/bin/codex.js` no longer computes or overrides `PATH`; it
only locates the native binary in the canonical package layout and
passes npm/bun management metadata.
- Added regression tests for `PATH` ordering, package layout detection,
and shell snapshot preservation of package path prepends.

## Verification

- `node --check codex-cli/bin/codex.js`
- `just test -p codex-install-context -p codex-arg0`
- `just test -p codex-core
user_shell_snapshot_preserves_package_path_prepend`
- `just test -p codex-core tools::runtimes::tests`
- `just bazel-lock-update`
- `just bazel-lock-check`
- `just fix -p codex-install-context -p codex-arg0 -p codex-core`
This commit is contained in:
Michael Bolin
2026-06-03 19:08:19 -07:00
committed by GitHub
Unverified
parent 80b65e9945
commit 6bcccb0ee6
11 changed files with 674 additions and 127 deletions
+96 -15
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;
@@ -86,17 +88,87 @@ pub(crate) fn strip_managed_proxy_env(env: &mut HashMap<String, String>) {
}
}
/// Prepends `path_entry` to `PATH`, removing duplicate and empty existing
/// entries.
///
/// Returns the updated `PATH` value when `env` was changed. Returns `None` when
/// `path_entry` is empty, leaving `env` untouched so an empty entry does not add
/// the current working directory to command lookup.
#[cfg(unix)]
fn prepend_path_entry(env: &mut HashMap<String, String>, path_entry: &str) -> String {
let updated_path = match env.get("PATH") {
Some(path) if !path.is_empty() => std::iter::once(path_entry)
.chain(path.split(':').filter(|entry| *entry != path_entry))
fn prepend_path_entry(env: &mut HashMap<String, String>, path_entry: &str) -> Option<String> {
if path_entry.is_empty() {
None
} else {
let updated_path = match env.get("PATH") {
Some(path) if !path.is_empty() => std::iter::once(path_entry)
.chain(
path.split(':')
.filter(|entry| !entry.is_empty() && *entry != path_entry),
)
.collect::<Vec<_>>()
.join(":"),
_ => path_entry.to_string(),
};
env.insert("PATH".to_string(), updated_path.clone());
Some(updated_path)
}
}
/// PATH entries owned by Codex runtime setup.
///
/// These are applied to the live exec environment immediately and replayed after
/// restoring a shell snapshot, unless the user explicitly overrides `PATH`.
#[derive(Debug, Default, Eq, PartialEq)]
pub(crate) struct RuntimePathPrepends {
entries: Vec<String>,
}
impl RuntimePathPrepends {
#[cfg(unix)]
pub(crate) fn prepend(&mut self, env: &mut HashMap<String, String>, path_entry: &Path) {
let path_entry = path_entry.to_string_lossy().to_string();
if prepend_path_entry(env, &path_entry).is_some() {
self.entries.retain(|entry| entry != &path_entry);
self.entries.push(path_entry);
}
}
fn shell_exports_after_snapshot(
&self,
explicit_env_overrides: &HashMap<String, String>,
) -> String {
if explicit_env_overrides.contains_key("PATH") {
return String::new();
}
self.entries
.iter()
.filter(|entry| !entry.is_empty())
.map(|entry| {
let entry = shell_single_quote(entry);
format!(
"if [ -n \"${{PATH:-}}\" ]; then export PATH='{entry}':\"$PATH\"; else export PATH='{entry}'; fi"
)
})
.collect::<Vec<_>>()
.join(":"),
_ => path_entry.to_string(),
.join("\n")
}
}
#[cfg(unix)]
pub(crate) fn apply_package_path_prepend(
env: &mut HashMap<String, String>,
runtime_path_prepends: &mut RuntimePathPrepends,
) {
let Some(path_dir) = InstallContext::current()
.package_layout
.as_ref()
.and_then(|package_layout| package_layout.path_dir.as_ref())
else {
return;
};
env.insert("PATH".to_string(), updated_path.clone());
updated_path
runtime_path_prepends.prepend(env, path_dir.as_path());
}
#[cfg(unix)]
@@ -107,21 +179,19 @@ pub(crate) fn prepend_zsh_fork_bin_to_path(
let zsh_bin_dir = shell_zsh_path
.parent()
.map(|path| path.to_string_lossy().to_string())?;
Some(prepend_path_entry(env, &zsh_bin_dir))
prepend_path_entry(env, &zsh_bin_dir)
}
#[cfg(unix)]
pub(crate) fn apply_zsh_fork_path_prepend(
env: &mut HashMap<String, String>,
explicit_env_overrides: &mut HashMap<String, String>,
runtime_path_prepends: &mut RuntimePathPrepends,
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);
runtime_path_prepends.prepend(env, zsh_bin_dir);
}
pub(crate) fn disable_powershell_profile_for_elevated_windows_sandbox(
@@ -172,12 +242,17 @@ pub(crate) fn disable_powershell_profile_for_elevated_windows_sandbox(
/// environment. We need access to both so snapshot restore logic can preserve
/// runtime-only vars like `CODEX_THREAD_ID` without pretending they came from
/// the explicit override policy.
///
/// `runtime_path_prepends` contains Codex-owned PATH entries already applied to
/// the live `env`; snapshot wrapping replays them after restoring the snapshot
/// PATH unless the user explicitly overrides `PATH`.
pub(crate) fn maybe_wrap_shell_lc_with_snapshot(
command: &[String],
session_shell: &Shell,
cwd: &AbsolutePathBuf,
explicit_env_overrides: &HashMap<String, String>,
env: &HashMap<String, String>,
runtime_path_prepends: &RuntimePathPrepends,
) -> Vec<String> {
if cfg!(windows) {
return command.to_vec();
@@ -219,8 +294,14 @@ pub(crate) fn maybe_wrap_shell_lc_with_snapshot(
}
let (override_captures, override_exports) = build_override_exports(&override_env);
let (proxy_captures, proxy_exports) = build_proxy_env_exports();
let runtime_path_prepend_exports =
runtime_path_prepends.shell_exports_after_snapshot(explicit_env_overrides);
let override_captures = join_shell_blocks([override_captures, proxy_captures]);
let override_exports = join_shell_blocks([override_exports, proxy_exports]);
let override_exports = join_shell_blocks([
override_exports,
proxy_exports,
runtime_path_prepend_exports,
]);
let rewritten_script = if override_exports.is_empty() {
format!(
"if . '{snapshot_path}' >/dev/null 2>&1; then :; fi\n\nexec '{original_shell}' -c '{original_script}'{trailing_args}"
+198 -9
View File
@@ -166,23 +166,106 @@ fn explicit_escalation_preserves_user_ca_env() {
);
}
#[cfg(unix)]
#[test]
fn runtime_path_prepends_records_runtime_path_prepend() {
let mut env = HashMap::from([("PATH".to_string(), "/usr/bin:/bin".to_string())]);
let mut runtime_path_prepends = RuntimePathPrepends::default();
runtime_path_prepends.prepend(&mut env, PathBuf::from("/package/codex-path").as_path());
assert_eq!(
env.get("PATH").map(String::as_str),
Some("/package/codex-path:/usr/bin:/bin"),
"runtime PATH prepend should update the live exec environment"
);
assert_eq!(
runtime_path_prepends.entries,
vec!["/package/codex-path"],
"runtime PATH prepend should be recorded for snapshot replay"
);
}
#[cfg(unix)]
#[test]
fn runtime_path_prepends_drops_empty_path_entries() {
let mut env = HashMap::from([(
"PATH".to_string(),
":/usr/bin:/package/codex-path::/bin:".to_string(),
)]);
let mut runtime_path_prepends = RuntimePathPrepends::default();
runtime_path_prepends.prepend(&mut env, PathBuf::from("/package/codex-path").as_path());
assert_eq!(
env.get("PATH").map(String::as_str),
Some("/package/codex-path:/usr/bin:/bin"),
"empty PATH entries should be dropped instead of preserving current-directory lookup"
);
assert_eq!(
runtime_path_prepends.entries,
vec!["/package/codex-path"],
"deduped runtime PATH prepend should still be recorded once"
);
}
#[cfg(unix)]
#[test]
fn runtime_path_prepends_ignores_empty_path_entry() {
let mut env = HashMap::from([("PATH".to_string(), "/usr/bin:/bin".to_string())]);
let mut runtime_path_prepends = RuntimePathPrepends::default();
runtime_path_prepends.prepend(&mut env, PathBuf::new().as_path());
assert_eq!(
env.get("PATH").map(String::as_str),
Some("/usr/bin:/bin"),
"empty runtime PATH prepend should leave PATH unchanged"
);
assert_eq!(
runtime_path_prepends,
RuntimePathPrepends::default(),
"empty runtime PATH prepend should not be recorded for snapshot replay"
);
}
#[cfg(unix)]
#[test]
fn prepend_zsh_fork_bin_to_path_ignores_empty_parent() {
let mut env = HashMap::from([("PATH".to_string(), "/usr/bin:/bin".to_string())]);
let result = prepend_zsh_fork_bin_to_path(&mut env, PathBuf::from("zsh").as_path());
assert_eq!(
result, None,
"zsh fork helper should not report a PATH update for an empty parent"
);
assert_eq!(
env.get("PATH").map(String::as_str),
Some("/usr/bin:/bin"),
"zsh fork helper should leave PATH unchanged when the parent is empty"
);
}
#[cfg(unix)]
#[test]
fn apply_zsh_fork_path_prepend_uses_shell_parent() {
let mut env = HashMap::from([("PATH".to_string(), "/usr/bin:/bin".to_string())]);
let mut explicit_env_overrides = HashMap::new();
let mut runtime_path_prepends = RuntimePathPrepends::default();
apply_zsh_fork_path_prepend(
&mut env,
&mut explicit_env_overrides,
&mut runtime_path_prepends,
PathBuf::from("/package/codex-resources/zsh/bin/zsh").as_path(),
);
let expected = "/package/codex-resources/zsh/bin:/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)
runtime_path_prepends,
RuntimePathPrepends {
entries: vec!["/package/codex-resources/zsh/bin".to_string()]
}
);
}
@@ -194,11 +277,11 @@ fn apply_zsh_fork_path_prepend_moves_existing_shell_parent_to_front() {
"/usr/bin:/package/codex-resources/zsh/bin:/bin:/package/codex-resources/zsh/bin"
.to_string(),
)]);
let mut explicit_env_overrides = HashMap::new();
let mut runtime_path_prepends = RuntimePathPrepends::default();
apply_zsh_fork_path_prepend(
&mut env,
&mut explicit_env_overrides,
&mut runtime_path_prepends,
PathBuf::from("/package/codex-resources/zsh/bin/zsh").as_path(),
);
@@ -206,6 +289,12 @@ fn apply_zsh_fork_path_prepend_moves_existing_shell_parent_to_front() {
env.get("PATH").map(String::as_str),
Some("/package/codex-resources/zsh/bin:/usr/bin:/bin")
);
assert_eq!(
runtime_path_prepends,
RuntimePathPrepends {
entries: vec!["/package/codex-resources/zsh/bin".to_string()]
}
);
}
#[test]
@@ -250,6 +339,7 @@ fn maybe_wrap_shell_lc_with_snapshot_bootstraps_in_user_shell() {
&dir.path().abs(),
&HashMap::new(),
&HashMap::new(),
&RuntimePathPrepends::default(),
);
assert_eq!(rewritten[0], "/bin/zsh");
@@ -281,6 +371,7 @@ fn maybe_wrap_shell_lc_with_snapshot_escapes_single_quotes() {
&dir.path().abs(),
&HashMap::new(),
&HashMap::new(),
&RuntimePathPrepends::default(),
);
assert!(rewritten[2].contains(r#"exec '/bin/bash' -c 'echo '"'"'hello'"'"''"#));
@@ -309,6 +400,7 @@ fn maybe_wrap_shell_lc_with_snapshot_uses_bash_bootstrap_shell() {
&dir.path().abs(),
&HashMap::new(),
&HashMap::new(),
&RuntimePathPrepends::default(),
);
assert_eq!(rewritten[0], "/bin/bash");
@@ -340,6 +432,7 @@ fn maybe_wrap_shell_lc_with_snapshot_uses_sh_bootstrap_shell() {
&dir.path().abs(),
&HashMap::new(),
&HashMap::new(),
&RuntimePathPrepends::default(),
);
assert_eq!(rewritten[0], "/bin/sh");
@@ -373,6 +466,7 @@ fn maybe_wrap_shell_lc_with_snapshot_preserves_trailing_args() {
&dir.path().abs(),
&HashMap::new(),
&HashMap::new(),
&RuntimePathPrepends::default(),
);
assert!(
@@ -408,6 +502,7 @@ fn maybe_wrap_shell_lc_with_snapshot_skips_when_cwd_mismatch() {
&command_cwd.abs(),
&HashMap::new(),
&HashMap::new(),
&RuntimePathPrepends::default(),
);
assert_eq!(rewritten, command);
@@ -437,6 +532,7 @@ fn maybe_wrap_shell_lc_with_snapshot_accepts_dot_alias_cwd() {
&command_cwd.abs(),
&HashMap::new(),
&HashMap::new(),
&RuntimePathPrepends::default(),
);
assert_eq!(rewritten[0], "/bin/zsh");
@@ -473,6 +569,7 @@ fn maybe_wrap_shell_lc_with_snapshot_restores_explicit_override_precedence() {
&dir.path().abs(),
&explicit_env_overrides,
&HashMap::from([("TEST_ENV_SNAPSHOT".to_string(), "worktree".to_string())]),
&RuntimePathPrepends::default(),
);
let output = Command::new(&rewritten[0])
.args(&rewritten[1..])
@@ -513,6 +610,7 @@ fn maybe_wrap_shell_lc_with_snapshot_restores_codex_thread_id_from_env() {
&dir.path().abs(),
&HashMap::new(),
&HashMap::from([("CODEX_THREAD_ID".to_string(), "nested-thread".to_string())]),
&RuntimePathPrepends::default(),
);
let output = Command::new(&rewritten[0])
.args(&rewritten[1..])
@@ -555,6 +653,7 @@ fn maybe_wrap_shell_lc_with_snapshot_restores_proxy_env_from_process_env() {
&dir.path().abs(),
&HashMap::new(),
&HashMap::new(),
&RuntimePathPrepends::default(),
);
let output = Command::new(&rewritten[0])
.args(&rewritten[1..])
@@ -612,6 +711,7 @@ fn maybe_wrap_shell_lc_with_snapshot_refreshes_codex_proxy_git_ssh_command() {
&dir.path().abs(),
&HashMap::new(),
&HashMap::new(),
&RuntimePathPrepends::default(),
);
let output = Command::new(&rewritten[0])
.args(&rewritten[1..])
@@ -657,6 +757,7 @@ fn maybe_wrap_shell_lc_with_snapshot_restores_custom_git_ssh_command() {
&dir.path().abs(),
&HashMap::new(),
&HashMap::new(),
&RuntimePathPrepends::default(),
);
let output = Command::new(&rewritten[0])
.args(&rewritten[1..])
@@ -703,6 +804,7 @@ fn maybe_wrap_shell_lc_with_snapshot_clears_stale_codex_git_ssh_command_without_
&dir.path().abs(),
&HashMap::new(),
&HashMap::new(),
&RuntimePathPrepends::default(),
);
let output = Command::new(&rewritten[0])
.args(&rewritten[1..])
@@ -740,6 +842,7 @@ fn maybe_wrap_shell_lc_with_snapshot_keeps_user_proxy_env_when_proxy_inactive()
&dir.path().abs(),
&HashMap::new(),
&HashMap::new(),
&RuntimePathPrepends::default(),
);
let mut command = Command::new(&rewritten[0]);
command.args(&rewritten[1..]);
@@ -793,6 +896,7 @@ fn maybe_wrap_shell_lc_with_snapshot_restores_live_env_when_snapshot_proxy_activ
"HTTP_PROXY".to_string(),
"http://user.proxy:8080".to_string(),
)]),
&RuntimePathPrepends::default(),
);
let output = Command::new(&rewritten[0])
.args(&rewritten[1..])
@@ -835,6 +939,7 @@ fn maybe_wrap_shell_lc_with_snapshot_keeps_snapshot_path_without_override() {
&dir.path().abs(),
&HashMap::new(),
&HashMap::new(),
&RuntimePathPrepends::default(),
);
let output = Command::new(&rewritten[0])
.args(&rewritten[1..])
@@ -872,6 +977,7 @@ fn maybe_wrap_shell_lc_with_snapshot_applies_explicit_path_override() {
&dir.path().abs(),
&explicit_env_overrides,
&HashMap::from([("PATH".to_string(), "/worktree/bin".to_string())]),
&RuntimePathPrepends::default(),
);
let output = Command::new(&rewritten[0])
.args(&rewritten[1..])
@@ -883,6 +989,84 @@ 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() -> anyhow::Result<()> {
let (stdout, package_path_dir) =
run_snapshot_path_probe_with_runtime_path_prepend(HashMap::new())?;
assert_eq!(
stdout,
format!("{}:/snapshot/bin", package_path_dir.display()),
"package path prepend should replay ahead of snapshot PATH"
);
Ok(())
}
#[cfg(unix)]
#[test]
fn maybe_wrap_shell_lc_with_snapshot_applies_runtime_path_prepend_after_explicit_path_override()
-> anyhow::Result<()> {
let (stdout, package_path_dir) = run_snapshot_path_probe_with_runtime_path_prepend(
HashMap::from([("PATH".to_string(), "/worktree/bin".to_string())]),
)?;
assert_eq!(
stdout,
format!("{}:/worktree/bin", package_path_dir.display()),
"explicit PATH override should suppress snapshot PATH while preserving runtime prepend"
);
Ok(())
}
#[cfg(unix)]
fn run_snapshot_path_probe_with_runtime_path_prepend(
explicit_env_overrides: HashMap<String, String>,
) -> anyhow::Result<(String, PathBuf)> {
let dir = tempdir()?;
let snapshot_path = dir.path().join("snapshot.sh");
std::fs::write(
&snapshot_path,
"# Snapshot file\nexport PATH='/snapshot/bin'\n",
)?;
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 runtime_path_prepends = RuntimePathPrepends::default();
runtime_path_prepends.prepend(&mut env, package_path_dir.as_path());
let rewritten = maybe_wrap_shell_lc_with_snapshot(
&command,
&session_shell,
&dir.path().abs(),
&explicit_env_overrides,
&env,
&runtime_path_prepends,
);
let path = env
.get("PATH")
.ok_or_else(|| anyhow::anyhow!("PATH should be set"))?;
let output = Command::new(&rewritten[0])
.args(&rewritten[1..])
.env("PATH", path)
.output()?;
assert!(output.status.success(), "command failed: {output:?}");
Ok((
String::from_utf8_lossy(&output.stdout).into_owned(),
package_path_dir,
))
}
#[cfg(unix)]
#[test]
fn maybe_wrap_shell_lc_with_snapshot_preserves_zsh_fork_path_prepend() {
@@ -912,14 +1096,16 @@ fn maybe_wrap_shell_lc_with_snapshot_preserves_zsh_fork_path_prepend() {
.join("zsh");
let zsh_bin_dir = zsh_path.parent().expect("zsh path should have parent");
let mut env = HashMap::from([("PATH".to_string(), "/worktree/bin".to_string())]);
let mut explicit_env_overrides = HashMap::new();
apply_zsh_fork_path_prepend(&mut env, &mut explicit_env_overrides, zsh_path.as_path());
let explicit_env_overrides = HashMap::new();
let mut runtime_path_prepends = RuntimePathPrepends::default();
apply_zsh_fork_path_prepend(&mut env, &mut runtime_path_prepends, zsh_path.as_path());
let rewritten = maybe_wrap_shell_lc_with_snapshot(
&command,
&session_shell,
&dir.path().abs(),
&explicit_env_overrides,
&env,
&runtime_path_prepends,
);
let output = Command::new(&rewritten[0])
.args(&rewritten[1..])
@@ -930,7 +1116,8 @@ fn maybe_wrap_shell_lc_with_snapshot_preserves_zsh_fork_path_prepend() {
assert!(output.status.success(), "command failed: {output:?}");
assert_eq!(
String::from_utf8_lossy(&output.stdout),
format!("{}:/worktree/bin", zsh_bin_dir.display())
format!("{}:/snapshot/bin", zsh_bin_dir.display()),
"zsh fork path prepend should replay ahead of snapshot PATH"
);
}
@@ -967,6 +1154,7 @@ fn maybe_wrap_shell_lc_with_snapshot_does_not_embed_override_values_in_argv() {
"OPENAI_API_KEY".to_string(),
"super-secret-value".to_string(),
)]),
&RuntimePathPrepends::default(),
);
assert!(!rewritten[2].contains("super-secret-value"));
@@ -1012,6 +1200,7 @@ fn maybe_wrap_shell_lc_with_snapshot_preserves_unset_override_variables() {
&dir.path().abs(),
&explicit_env_overrides,
&HashMap::new(),
&RuntimePathPrepends::default(),
);
let output = Command::new(&rewritten[0])
+12 -4
View File
@@ -20,6 +20,7 @@ use crate::shell::ShellType;
use crate::tools::flat_tool_name;
use crate::tools::network_approval::NetworkApprovalMode;
use crate::tools::network_approval::NetworkApprovalSpec;
use crate::tools::runtimes::RuntimePathPrepends;
#[cfg(unix)]
use crate::tools::runtimes::apply_zsh_fork_path_prepend;
use crate::tools::runtimes::build_sandbox_command;
@@ -249,22 +250,29 @@ impl ToolRuntime<ShellRequest, ExecToolCallOutput> for ShellRuntime {
let env = exec_env_for_sandbox_permissions(&req.env, sandbox_permissions);
let explicit_env_overrides = req.explicit_env_overrides.clone();
#[cfg(unix)]
let (env, explicit_env_overrides) = {
let (env, runtime_path_prepends) = {
let mut env = env;
let mut explicit_env_overrides = explicit_env_overrides;
let mut runtime_path_prepends = RuntimePathPrepends::default();
crate::tools::runtimes::apply_package_path_prepend(
&mut env,
&mut runtime_path_prepends,
);
if self.backend == ShellRuntimeBackend::ShellCommandZshFork
&& let Some(shell_zsh_path) = ctx.session.services.shell_zsh_path.as_deref()
{
apply_zsh_fork_path_prepend(&mut env, &mut explicit_env_overrides, shell_zsh_path);
apply_zsh_fork_path_prepend(&mut env, &mut runtime_path_prepends, shell_zsh_path);
}
(env, explicit_env_overrides)
(env, runtime_path_prepends)
};
#[cfg(not(unix))]
let runtime_path_prepends = RuntimePathPrepends::default();
let command = maybe_wrap_shell_lc_with_snapshot(
&req.command,
session_shell.as_ref(),
&req.cwd,
&explicit_env_overrides,
&env,
&runtime_path_prepends,
);
let command = disable_powershell_profile_for_elevated_windows_sandbox(
&command,
@@ -17,6 +17,7 @@ use crate::shell::ShellType;
use crate::tools::flat_tool_name;
use crate::tools::network_approval::NetworkApprovalMode;
use crate::tools::network_approval::NetworkApprovalSpec;
use crate::tools::runtimes::RuntimePathPrepends;
#[cfg(unix)]
use crate::tools::runtimes::apply_zsh_fork_path_prepend;
use crate::tools::runtimes::build_sandbox_command;
@@ -278,20 +279,28 @@ 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;
let runtime_path_prepends = {
let mut runtime_path_prepends = RuntimePathPrepends::default();
if !environment_is_remote {
crate::tools::runtimes::apply_package_path_prepend(
&mut env,
&mut runtime_path_prepends,
);
}
if let UnifiedExecShellMode::ZshFork(zsh_fork_config) = &self.shell_mode {
apply_zsh_fork_path_prepend(
&mut env,
&mut explicit_env_overrides,
&mut runtime_path_prepends,
zsh_fork_config.shell_zsh_path.as_path(),
);
}
explicit_env_overrides
runtime_path_prepends
};
let environment_is_remote = req.environment.is_remote();
#[cfg(not(unix))]
let runtime_path_prepends = RuntimePathPrepends::default();
let command = if environment_is_remote {
base_command.to_vec()
} else {
@@ -301,6 +310,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
&req.cwd,
&explicit_env_overrides,
&env,
&runtime_path_prepends,
)
};
let command = disable_powershell_profile_for_elevated_windows_sandbox(