Preserve Unicode paths in Windows batch aliases (#40570)

## Why

Windows batch aliases can fail to invoke Codex when the executable path contains
characters that are not representable in the active console code page.

## What changed

- Generate same-volume executable paths relative to the batch file using `%~dp0`.
- Keep absolute paths as a fallback when the executable and alias are on different
  volumes.

## Testing

Added Windows tests covering a Unicode profile path under code page 437 and an
executable on a different volume.

GitOrigin-RevId: 10093bbc9f19e4fbe2e22d9397e211a113302f45
This commit is contained in:
zm-oai
2026-08-25 07:37:42 +00:00
committed by copyberry
parent 4347f94d55
commit d52478c52e
3 changed files with 57 additions and 1 deletions
+1
View File
@@ -2280,6 +2280,7 @@ dependencies = [
"codex-utils-home-dir",
"codex-windows-sandbox",
"dotenvy",
"pathdiff",
"pretty_assertions",
"tempfile",
"tokio",
+1
View File
@@ -28,6 +28,7 @@ tokio = { workspace = true, features = ["rt-multi-thread"] }
[target.'cfg(windows)'.dependencies]
codex-windows-sandbox = { workspace = true }
pathdiff = { workspace = true }
[dev-dependencies]
pretty_assertions = { workspace = true }
+55 -1
View File
@@ -403,7 +403,7 @@ fn prepare_path_entry_for_codex_aliases(
#[cfg(windows)]
{
let batch_script = path.join(format!("{filename}.bat"));
let exe = exe.display();
let exe = windows_batch_executable_path(&exe, path);
std::fs::write(
&batch_script,
format!(
@@ -447,6 +447,14 @@ fn prepare_path_entry_for_codex_aliases(
))
}
#[cfg(windows)]
fn windows_batch_executable_path(executable: &Path, alias_directory: &Path) -> String {
pathdiff::diff_paths(executable, alias_directory)
.filter(|relative_path| relative_path.is_relative())
.map(|relative_path| format!("%~dp0{}", relative_path.display()))
.unwrap_or_else(|| executable.display().to_string())
}
fn path_env_with_package_path_dir(
install_context: &InstallContext,
existing_path: Option<OsString>,
@@ -540,6 +548,8 @@ mod tests {
use pretty_assertions::assert_eq;
use std::fs;
use std::fs::File;
#[cfg(windows)]
use std::os::windows::process::CommandExt;
use std::path::Path;
use std::path::PathBuf;
use tempfile::TempDir;
@@ -593,6 +603,50 @@ mod tests {
})
}
#[cfg(windows)]
#[test]
fn windows_batch_alias_preserves_unicode_executable_paths() -> anyhow::Result<()> {
let root = TempDir::new()?;
let profile = root.path().join("用户");
let alias_directory = profile.join(".codex").join("tmp").join("arg0");
let executable_directory = profile.join("bin");
fs::create_dir_all(&alias_directory)?;
fs::create_dir_all(&executable_directory)?;
let system_root = std::env::var_os("SystemRoot")
.ok_or_else(|| anyhow::anyhow!("missing Windows system root"))?;
let command_shell = PathBuf::from(system_root).join("System32").join("cmd.exe");
let executable = executable_directory.join("cmd.exe");
fs::copy(&command_shell, &executable)?;
let batch_path = alias_directory.join("apply_patch.bat");
let executable_path = super::windows_batch_executable_path(&executable, &alias_directory);
fs::write(
&batch_path,
format!("@echo off\r\n\"{executable_path}\" /d /c exit 37\r\n"),
)?;
let output = std::process::Command::new(command_shell)
.args(["/d", "/c"])
.raw_arg(format!("chcp 437>nul & call \"{}\"", batch_path.display()))
.output()?;
assert_eq!(output.status.code(), Some(37));
Ok(())
}
#[cfg(windows)]
#[test]
fn windows_batch_alias_preserves_cross_volume_executable_paths() {
assert_eq!(
super::windows_batch_executable_path(
Path::new(r"D:\Tools\codex.exe"),
Path::new(r"C:\Users\person\.codex\tmp\arg0"),
),
r"D:\Tools\codex.exe",
);
}
#[test]
fn linux_sandbox_exe_path_prefers_codex_linux_sandbox_alias() -> std::io::Result<()> {
let temp_dir = TempDir::new()?;