Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion codex-cli/bin/codex.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,10 @@ const binaryPath = path.join(archRoot, "codex", codexBinaryName);
function getUpdatedPath(newDirs) {
const pathSep = process.platform === "win32" ? ";" : ":";
const existingPath = process.env.PATH || "";
// Preserve caller PATH precedence by appending any additional dirs.
const updatedPath = [
...newDirs,
...existingPath.split(pathSep).filter(Boolean),
...newDirs,
].join(pathSep);
return updatedPath;
}
Expand Down
4 changes: 3 additions & 1 deletion codex-rs/arg0/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,11 @@ fn prepend_path_entry_for_apply_patch() -> std::io::Result<TempDir> {
const PATH_SEPARATOR: &str = ";";

let path_element = path.display();
// Append our temp dir to PATH rather than prepending to avoid
// disturbing user PATH ordering (e.g., virtualenvs, shims).
let updated_path_env_var = match std::env::var("PATH") {
Ok(existing_path) => {
format!("{path_element}{PATH_SEPARATOR}{existing_path}")
format!("{existing_path}{PATH_SEPARATOR}{path_element}")
}
Err(_) => {
format!("{path_element}")
Expand Down
58 changes: 56 additions & 2 deletions codex-rs/rmcp-client/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,37 @@ pub(crate) fn create_env_for_mcp_server(
extra_env: Option<HashMap<String, String>>,
env_vars: &[String],
) -> HashMap<String, String> {
DEFAULT_ENV_VARS
#[cfg(windows)]
let mut map: HashMap<String, String> = DEFAULT_ENV_VARS
.iter()
.copied()
.chain(env_vars.iter().map(String::as_str))
.filter_map(|var| env::var(var).ok().map(|value| (var.to_string(), value)))
.chain(extra_env.unwrap_or_default())
.collect()
.collect();

#[cfg(not(windows))]
let map: HashMap<String, String> = DEFAULT_ENV_VARS
.iter()
.copied()
.chain(env_vars.iter().map(String::as_str))
.filter_map(|var| env::var(var).ok().map(|value| (var.to_string(), value)))
.chain(extra_env.unwrap_or_default())
.collect();

#[cfg(windows)]
{
// Some Windows programs look up the canonical-cased key `Path`.
// Ensure both `PATH` and `Path` are present with identical values.
if let Some(path_val) = map.get("PATH").cloned() {
map.entry("Path".to_string()).or_insert(path_val);
} else if let Ok(system_path) = env::var("PATH") {
map.insert("PATH".to_string(), system_path.clone());
map.entry("Path".to_string()).or_insert(system_path);
}
}

map
}

pub(crate) fn build_default_headers(
Expand Down Expand Up @@ -171,13 +195,33 @@ pub(crate) const DEFAULT_ENV_VARS: &[&str] = &[

#[cfg(windows)]
pub(crate) const DEFAULT_ENV_VARS: &[&str] = &[
// Core path resolution
"PATH",
"PATHEXT",
// Shell and system roots
"COMSPEC",
"SYSTEMROOT",
"SYSTEMDRIVE",
// User context and profiles
"USERNAME",
"USERDOMAIN",
"USERPROFILE",
"HOMEDRIVE",
"HOMEPATH",
// Program locations
"PROGRAMFILES",
"PROGRAMFILES(X86)",
"PROGRAMW6432",
"PROGRAMDATA",
// App data and caches
"LOCALAPPDATA",
"APPDATA",
// Temp locations
"TEMP",
"TMP",
// Common shells/pwsh hints
"POWERSHELL",
"PWSH",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cpjet64 if we separate these changes, i'm happy to stamp the DEFAULT_ENV_VARS change and merge it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dylan-hurd-oai Happy to do so but was hoping it would pass since my next PR for this issue is virtual shells like Python venv and such and the non-PATH fix is the foundation.

I will make this PR into a dedicated path fix in the meantime.

Thank you!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not opposed to switching to appending here, but the impacts of the path change are just more subtle, and might impact the success rate of apply_patch calls. Adding env vars to unblock better MCP support on windows feels much lower risk, so let's get that improvement in and we can focus on this PATH change here.

Copy link
Contributor Author

@cpjet64 cpjet64 Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dylan-hurd-oai Understood and I agree the env var change is definitely more low risk because I didn't consider the apply-patch impact in that manner. I'll split them now and get you a new commit in a few minutes! Later I'll dig more into apply_patch to make sure before I push a new PR for it. Thanks for that!

];

#[cfg(test)]
Expand Down Expand Up @@ -223,6 +267,16 @@ mod tests {
}
}

#[test]
#[cfg(windows)]
fn windows_path_alias_is_present() {
let _guard = EnvVarGuard::set("PATH", r"C:\\Windows;C:\\Windows\\System32");
let env = create_env_for_mcp_server(None, &[]);
assert!(env.contains_key("PATH"));
assert!(env.contains_key("Path"));
assert_eq!(env.get("PATH"), env.get("Path"));
}

#[tokio::test]
async fn create_env_honors_overrides() {
let value = "custom".to_string();
Expand Down