From d67ae2297cebeb73f06261bb7ef15e514066f001 Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Mon, 10 Nov 2025 15:29:39 -0800 Subject: [PATCH 1/6] Alias codex-linux-sandbox --- codex-rs/Cargo.lock | 3 + codex-rs/arg0/src/lib.rs | 10 ++- codex-rs/core/Cargo.toml | 4 +- .../core/src/unified_exec/session_manager.rs | 14 +++- codex-rs/core/tests/suite/mod.rs | 25 +++++++ codex-rs/core/tests/suite/unified_exec.rs | 74 ++++++++++++++++++- codex-rs/utils/pty/Cargo.toml | 7 +- codex-rs/utils/pty/src/lib.rs | 4 +- 8 files changed, 125 insertions(+), 16 deletions(-) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index ec6420a05f..8522738b6a 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1063,6 +1063,7 @@ dependencies = [ "chrono", "codex-app-server-protocol", "codex-apply-patch", + "codex-arg0", "codex-async-utils", "codex-file-search", "codex-git", @@ -1077,6 +1078,7 @@ dependencies = [ "codex-windows-sandbox", "core-foundation 0.9.4", "core_test_support", + "ctor 0.5.0", "dirs", "dunce", "env-flags", @@ -1520,6 +1522,7 @@ dependencies = [ "anyhow", "portable-pty", "tokio", + "tracing", ] [[package]] diff --git a/codex-rs/arg0/src/lib.rs b/codex-rs/arg0/src/lib.rs index e70ff2df64..decf60f374 100644 --- a/codex-rs/arg0/src/lib.rs +++ b/codex-rs/arg0/src/lib.rs @@ -79,7 +79,7 @@ where // Retain the TempDir so it exists for the lifetime of the invocation of // this executable. Admittedly, we could invoke `keep()` on it, but it // would be nice to avoid leaving temporary directories behind, if possible. - let _path_entry = match prepend_path_entry_for_apply_patch() { + let _path_entry = match prepend_path_entry_for_codex_aliases() { Ok(path_entry) => Some(path_entry), Err(err) => { // It is possible that Codex will proceed successfully even if @@ -144,11 +144,15 @@ where /// /// IMPORTANT: This function modifies the PATH environment variable, so it MUST /// be called before multiple threads are spawned. -fn prepend_path_entry_for_apply_patch() -> std::io::Result { +pub fn prepend_path_entry_for_codex_aliases() -> std::io::Result { let temp_dir = TempDir::new()?; let path = temp_dir.path(); - for filename in &[APPLY_PATCH_ARG0, MISSPELLED_APPLY_PATCH_ARG0] { + for filename in &[ + APPLY_PATCH_ARG0, + MISSPELLED_APPLY_PATCH_ARG0, + LINUX_SANDBOX_ARG0, + ] { let exe = std::env::current_exe()?; #[cfg(unix)] diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index 17a7a16609..33caa69103 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -32,6 +32,8 @@ codex-utils-pty = { workspace = true } codex-utils-readiness = { workspace = true } codex-utils-string = { workspace = true } codex-utils-tokenizer = { workspace = true } +codex_windows_sandbox = { package = "codex-windows-sandbox", path = "../windows-sandbox-rs" } +ctor = { workspace = true } dirs = { workspace = true } dunce = { workspace = true } env-flags = { workspace = true } @@ -83,7 +85,6 @@ tree-sitter-bash = { workspace = true } uuid = { workspace = true, features = ["serde", "v4", "v5"] } which = { workspace = true } wildmatch = { workspace = true } -codex_windows_sandbox = { package = "codex-windows-sandbox", path = "../windows-sandbox-rs" } [target.'cfg(target_os = "linux")'.dependencies] @@ -104,6 +105,7 @@ openssl-sys = { workspace = true, features = ["vendored"] } [dev-dependencies] assert_cmd = { workspace = true } assert_matches = { workspace = true } +codex-arg0 = { workspace = true } core_test_support = { workspace = true } escargot = { workspace = true } image = { workspace = true, features = ["jpeg", "png"] } diff --git a/codex-rs/core/src/unified_exec/session_manager.rs b/codex-rs/core/src/unified_exec/session_manager.rs index bc10fc2bd8..55a5f589a3 100644 --- a/codex-rs/core/src/unified_exec/session_manager.rs +++ b/codex-rs/core/src/unified_exec/session_manager.rs @@ -300,10 +300,16 @@ impl UnifiedExecSessionManager { .command .split_first() .ok_or(UnifiedExecError::MissingCommandLine)?; - let spawned = - codex_utils_pty::spawn_pty_process(program, args, env.cwd.as_path(), &env.env) - .await - .map_err(|err| UnifiedExecError::create_session(err.to_string()))?; + + let spawned = codex_utils_pty::spawn_pty_process( + program, + args, + env.cwd.as_path(), + &env.env, + &env.arg0, + ) + .await + .map_err(|err| UnifiedExecError::create_session(err.to_string()))?; UnifiedExecSession::from_spawned(spawned, env.sandbox).await } diff --git a/codex-rs/core/tests/suite/mod.rs b/codex-rs/core/tests/suite/mod.rs index a884167471..2d3ed83d1f 100644 --- a/codex-rs/core/tests/suite/mod.rs +++ b/codex-rs/core/tests/suite/mod.rs @@ -1,4 +1,29 @@ // Aggregates all former standalone integration tests as modules. +use codex_arg0::arg0_dispatch_or_else; +use codex_arg0::prepend_path_entry_for_codex_aliases; +use ctor::ctor; +use tempfile::TempDir; + +// This code runs before any other tests are run. +// It allows the test binary to behave like codex and dispatch to apply_patch and codex-linux-sandbox +// based on the arg0. +#[ctor] +pub static CODEX_ALIASES_TEMP_DIR: TempDir = unsafe { + let mut should_exit = true; + + #[allow(clippy::unwrap_used)] + arg0_dispatch_or_else(|_| async { + should_exit = false; + Ok(()) + }) + .unwrap(); + if should_exit { + std::process::exit(0); + } + + #[allow(clippy::unwrap_used)] + prepend_path_entry_for_codex_aliases().unwrap() +}; #[cfg(not(target_os = "windows"))] mod abort_tasks; diff --git a/codex-rs/core/tests/suite/unified_exec.rs b/codex-rs/core/tests/suite/unified_exec.rs index cd8b338200..cbd2d1533c 100644 --- a/codex-rs/core/tests/suite/unified_exec.rs +++ b/codex-rs/core/tests/suite/unified_exec.rs @@ -66,7 +66,7 @@ fn parse_unified_exec_output(raw: &str) -> Result { let cleaned = raw.trim_matches('\r'); let captures = regex .captures(cleaned) - .ok_or_else(|| anyhow::anyhow!("missing Output section in unified exec output"))?; + .ok_or_else(|| anyhow::anyhow!("missing Output section in unified exec output {raw}"))?; let chunk_id = captures .name("chunk_id") @@ -1451,3 +1451,75 @@ PY Ok(()) } + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn unified_exec_runs_under_sandbox() -> Result<()> { + skip_if_no_network!(Ok(())); + skip_if_sandbox!(Ok(())); + + let server = start_mock_server().await; + + let mut builder = test_codex().with_config(|config| { + config.features.enable(Feature::UnifiedExec); + }); + let TestCodex { + codex, + cwd, + session_configured, + .. + } = builder.build(&server).await?; + + let call_id = "uexec"; + let args = serde_json::json!({ + "cmd": "echo 'hello'", + "yield_time_ms": 500, + }); + + let responses = vec![ + sse(vec![ + ev_response_created("resp-1"), + ev_function_call(call_id, "exec_command", &serde_json::to_string(&args)?), + ev_completed("resp-1"), + ]), + sse(vec![ + ev_assistant_message("msg-1", "done"), + ev_completed("resp-2"), + ]), + ]; + mount_sse_sequence(&server, responses).await; + + let session_model = session_configured.model.clone(); + + codex + .submit(Op::UserTurn { + items: vec![UserInput::Text { + text: "summarize large output".into(), + }], + final_output_json_schema: None, + cwd: cwd.path().to_path_buf(), + approval_policy: AskForApproval::Never, + // Important! + sandbox_policy: SandboxPolicy::ReadOnly, + model: session_model, + effort: None, + summary: ReasoningSummary::Auto, + }) + .await?; + + wait_for_event(&codex, |event| matches!(event, EventMsg::TaskComplete(_))).await; + + let requests = server.received_requests().await.expect("recorded requests"); + assert!(!requests.is_empty(), "expected at least one POST request"); + + let bodies = requests + .iter() + .map(|req| req.body_json::().expect("request json")) + .collect::>(); + + let outputs = collect_tool_outputs(&bodies)?; + let output = outputs.get(call_id).expect("missing output"); + + assert_regex_match(r"hello\w+", &output.output); + + Ok(()) +} diff --git a/codex-rs/utils/pty/Cargo.toml b/codex-rs/utils/pty/Cargo.toml index 117d626318..54d37ecd56 100644 --- a/codex-rs/utils/pty/Cargo.toml +++ b/codex-rs/utils/pty/Cargo.toml @@ -9,8 +9,5 @@ workspace = true [dependencies] anyhow = { workspace = true } portable-pty = { workspace = true } -tokio = { workspace = true, features = [ - "macros", - "rt-multi-thread", - "sync", -] } +tokio = { workspace = true, features = ["macros", "rt-multi-thread", "sync"] } +tracing = { workspace = true, features = ["log"] } diff --git a/codex-rs/utils/pty/src/lib.rs b/codex-rs/utils/pty/src/lib.rs index 740a502ba9..c91b6b0a2b 100644 --- a/codex-rs/utils/pty/src/lib.rs +++ b/codex-rs/utils/pty/src/lib.rs @@ -111,6 +111,7 @@ pub async fn spawn_pty_process( args: &[String], cwd: &Path, env: &HashMap, + arg0: &Option, ) -> Result { if program.is_empty() { anyhow::bail!("missing program for PTY spawn"); @@ -124,7 +125,7 @@ pub async fn spawn_pty_process( pixel_height: 0, })?; - let mut command_builder = CommandBuilder::new(program); + let mut command_builder = CommandBuilder::new(arg0.as_ref().unwrap_or(&program.to_string())); command_builder.cwd(cwd); command_builder.env_clear(); for arg in args { @@ -133,7 +134,6 @@ pub async fn spawn_pty_process( for (key, value) in env { command_builder.env(key, value); } - let mut child = pair.slave.spawn_command(command_builder)?; let killer = child.clone_killer(); From bfb8bd11ce2888ceae20a06bdd2f77012151eb40 Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Mon, 10 Nov 2025 15:33:05 -0800 Subject: [PATCH 2/6] pty: reformat tokio dependency for improved readability in Cargo.toml --- codex-rs/utils/pty/Cargo.toml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/codex-rs/utils/pty/Cargo.toml b/codex-rs/utils/pty/Cargo.toml index 54d37ecd56..117d626318 100644 --- a/codex-rs/utils/pty/Cargo.toml +++ b/codex-rs/utils/pty/Cargo.toml @@ -9,5 +9,8 @@ workspace = true [dependencies] anyhow = { workspace = true } portable-pty = { workspace = true } -tokio = { workspace = true, features = ["macros", "rt-multi-thread", "sync"] } -tracing = { workspace = true, features = ["log"] } +tokio = { workspace = true, features = [ + "macros", + "rt-multi-thread", + "sync", +] } From 526cacca3eccc53b774ab5a3ab9c63559fbf9409 Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Mon, 10 Nov 2025 15:33:29 -0800 Subject: [PATCH 3/6] codex-rs: remove unused tracing dependency from lockfile --- codex-rs/Cargo.lock | 1 - 1 file changed, 1 deletion(-) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 8522738b6a..1a0bdf27ab 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1522,7 +1522,6 @@ dependencies = [ "anyhow", "portable-pty", "tokio", - "tracing", ] [[package]] From ac539e06aa09440161e2d56da61b286f689425da Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Mon, 10 Nov 2025 15:45:27 -0800 Subject: [PATCH 4/6] core/tests: document that test binary aliasing logic does not work on ARM Add comments noting that the ctor logic for test binary aliasing is not supported on ARM architecture, and conditionally skip affected test on ARM targets. --- codex-rs/core/tests/suite/mod.rs | 1 + codex-rs/core/tests/suite/unified_exec.rs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/codex-rs/core/tests/suite/mod.rs b/codex-rs/core/tests/suite/mod.rs index 2d3ed83d1f..3bc399ff6e 100644 --- a/codex-rs/core/tests/suite/mod.rs +++ b/codex-rs/core/tests/suite/mod.rs @@ -7,6 +7,7 @@ use tempfile::TempDir; // This code runs before any other tests are run. // It allows the test binary to behave like codex and dispatch to apply_patch and codex-linux-sandbox // based on the arg0. +// NOTE: this doesn't work on ARM #[ctor] pub static CODEX_ALIASES_TEMP_DIR: TempDir = unsafe { let mut should_exit = true; diff --git a/codex-rs/core/tests/suite/unified_exec.rs b/codex-rs/core/tests/suite/unified_exec.rs index cbd2d1533c..d29414faaa 100644 --- a/codex-rs/core/tests/suite/unified_exec.rs +++ b/codex-rs/core/tests/suite/unified_exec.rs @@ -1368,6 +1368,8 @@ async fn unified_exec_timeout_and_followup_poll() -> Result<()> { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] +// Skipped on arm because the ctor logic to handle arg0 doesn't work on ARM +#[cfg(not(target_arch = "arm"))] async fn unified_exec_formats_large_output_summary() -> Result<()> { skip_if_no_network!(Ok(())); skip_if_sandbox!(Ok(())); From 9ed5a0b339f3415ba582e9d231f67c7fdbbbc655 Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Mon, 10 Nov 2025 16:02:16 -0800 Subject: [PATCH 5/6] refactor(arg0): extract PATH entry logic into arg0_dispatch Move path entry and alias dispatch logic from arg0_dispatch_or_else to a new arg0_dispatch function, allowing reuse in test setup. Update tests to use arg0_dispatch directly. Fix minor regex in unified_exec test. Move 'ctor' dev-dependency to dev-dependencies only in core crate. --- codex-rs/arg0/src/lib.rs | 62 +++++++++++++---------- codex-rs/core/Cargo.toml | 2 +- codex-rs/core/tests/suite/mod.rs | 17 +------ codex-rs/core/tests/suite/unified_exec.rs | 2 +- 4 files changed, 38 insertions(+), 45 deletions(-) diff --git a/codex-rs/arg0/src/lib.rs b/codex-rs/arg0/src/lib.rs index decf60f374..d5620f5631 100644 --- a/codex-rs/arg0/src/lib.rs +++ b/codex-rs/arg0/src/lib.rs @@ -11,32 +11,7 @@ const LINUX_SANDBOX_ARG0: &str = "codex-linux-sandbox"; const APPLY_PATCH_ARG0: &str = "apply_patch"; const MISSPELLED_APPLY_PATCH_ARG0: &str = "applypatch"; -/// While we want to deploy the Codex CLI as a single executable for simplicity, -/// we also want to expose some of its functionality as distinct CLIs, so we use -/// the "arg0 trick" to determine which CLI to dispatch. This effectively allows -/// us to simulate deploying multiple executables as a single binary on Mac and -/// Linux (but not Windows). -/// -/// When the current executable is invoked through the hard-link or alias named -/// `codex-linux-sandbox` we *directly* execute -/// [`codex_linux_sandbox::run_main`] (which never returns). Otherwise we: -/// -/// 1. Load `.env` values from `~/.codex/.env` before creating any threads. -/// 2. Construct a Tokio multi-thread runtime. -/// 3. Derive the path to the current executable (so children can re-invoke the -/// sandbox) when running on Linux. -/// 4. Execute the provided async `main_fn` inside that runtime, forwarding any -/// error. Note that `main_fn` receives `codex_linux_sandbox_exe: -/// Option`, as an argument, which is generally needed as part of -/// constructing [`codex_core::config::Config`]. -/// -/// This function should be used to wrap any `main()` function in binary crates -/// in this workspace that depends on these helper CLIs. -pub fn arg0_dispatch_or_else(main_fn: F) -> anyhow::Result<()> -where - F: FnOnce(Option) -> Fut, - Fut: Future>, -{ +pub fn arg0_dispatch() -> Option { // Determine if we were invoked via the special alias. let mut args = std::env::args_os(); let argv0 = args.next().unwrap_or_default(); @@ -79,7 +54,8 @@ where // Retain the TempDir so it exists for the lifetime of the invocation of // this executable. Admittedly, we could invoke `keep()` on it, but it // would be nice to avoid leaving temporary directories behind, if possible. - let _path_entry = match prepend_path_entry_for_codex_aliases() { + + match prepend_path_entry_for_codex_aliases() { Ok(path_entry) => Some(path_entry), Err(err) => { // It is possible that Codex will proceed successfully even if @@ -87,7 +63,36 @@ where eprintln!("WARNING: proceeding, even though we could not update PATH: {err}"); None } - }; + } +} + +/// While we want to deploy the Codex CLI as a single executable for simplicity, +/// we also want to expose some of its functionality as distinct CLIs, so we use +/// the "arg0 trick" to determine which CLI to dispatch. This effectively allows +/// us to simulate deploying multiple executables as a single binary on Mac and +/// Linux (but not Windows). +/// +/// When the current executable is invoked through the hard-link or alias named +/// `codex-linux-sandbox` we *directly* execute +/// [`codex_linux_sandbox::run_main`] (which never returns). Otherwise we: +/// +/// 1. Load `.env` values from `~/.codex/.env` before creating any threads. +/// 2. Construct a Tokio multi-thread runtime. +/// 3. Derive the path to the current executable (so children can re-invoke the +/// sandbox) when running on Linux. +/// 4. Execute the provided async `main_fn` inside that runtime, forwarding any +/// error. Note that `main_fn` receives `codex_linux_sandbox_exe: +/// Option`, as an argument, which is generally needed as part of +/// constructing [`codex_core::config::Config`]. +/// +/// This function should be used to wrap any `main()` function in binary crates +/// in this workspace that depends on these helper CLIs. +pub fn arg0_dispatch_or_else(main_fn: F) -> anyhow::Result<()> +where + F: FnOnce(Option) -> Fut, + Fut: Future>, +{ + let _path_entry = arg0_dispatch(); // Regular invocation – create a Tokio runtime and execute the provided // async entry-point. @@ -151,6 +156,7 @@ pub fn prepend_path_entry_for_codex_aliases() -> std::io::Result { for filename in &[ APPLY_PATCH_ARG0, MISSPELLED_APPLY_PATCH_ARG0, + #[cfg(target_os = "linux")] LINUX_SANDBOX_ARG0, ] { let exe = std::env::current_exe()?; diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index 33caa69103..dd0a3dd4be 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -33,7 +33,6 @@ codex-utils-readiness = { workspace = true } codex-utils-string = { workspace = true } codex-utils-tokenizer = { workspace = true } codex_windows_sandbox = { package = "codex-windows-sandbox", path = "../windows-sandbox-rs" } -ctor = { workspace = true } dirs = { workspace = true } dunce = { workspace = true } env-flags = { workspace = true } @@ -107,6 +106,7 @@ assert_cmd = { workspace = true } assert_matches = { workspace = true } codex-arg0 = { workspace = true } core_test_support = { workspace = true } +ctor = { workspace = true } escargot = { workspace = true } image = { workspace = true, features = ["jpeg", "png"] } maplit = { workspace = true } diff --git a/codex-rs/core/tests/suite/mod.rs b/codex-rs/core/tests/suite/mod.rs index 3bc399ff6e..a19bfa2505 100644 --- a/codex-rs/core/tests/suite/mod.rs +++ b/codex-rs/core/tests/suite/mod.rs @@ -1,6 +1,5 @@ // Aggregates all former standalone integration tests as modules. -use codex_arg0::arg0_dispatch_or_else; -use codex_arg0::prepend_path_entry_for_codex_aliases; +use codex_arg0::arg0_dispatch; use ctor::ctor; use tempfile::TempDir; @@ -10,20 +9,8 @@ use tempfile::TempDir; // NOTE: this doesn't work on ARM #[ctor] pub static CODEX_ALIASES_TEMP_DIR: TempDir = unsafe { - let mut should_exit = true; - - #[allow(clippy::unwrap_used)] - arg0_dispatch_or_else(|_| async { - should_exit = false; - Ok(()) - }) - .unwrap(); - if should_exit { - std::process::exit(0); - } - #[allow(clippy::unwrap_used)] - prepend_path_entry_for_codex_aliases().unwrap() + arg0_dispatch().unwrap() }; #[cfg(not(target_os = "windows"))] diff --git a/codex-rs/core/tests/suite/unified_exec.rs b/codex-rs/core/tests/suite/unified_exec.rs index d29414faaa..1225b51ccd 100644 --- a/codex-rs/core/tests/suite/unified_exec.rs +++ b/codex-rs/core/tests/suite/unified_exec.rs @@ -1521,7 +1521,7 @@ async fn unified_exec_runs_under_sandbox() -> Result<()> { let outputs = collect_tool_outputs(&bodies)?; let output = outputs.get(call_id).expect("missing output"); - assert_regex_match(r"hello\w+", &output.output); + assert_regex_match("hello[\r\n]+", &output.output); Ok(()) } From e83434fa73ad6a9511c1ad420d113873772c582a Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Mon, 10 Nov 2025 16:50:03 -0800 Subject: [PATCH 6/6] arg0: move TempDir retention comment to correct function core: fix codex-windows-sandbox dependency name pty: add newline after env insertion for clarity --- codex-rs/arg0/src/lib.rs | 7 +++---- codex-rs/core/Cargo.toml | 2 +- codex-rs/utils/pty/src/lib.rs | 1 + 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/codex-rs/arg0/src/lib.rs b/codex-rs/arg0/src/lib.rs index d5620f5631..6b60536413 100644 --- a/codex-rs/arg0/src/lib.rs +++ b/codex-rs/arg0/src/lib.rs @@ -51,10 +51,6 @@ pub fn arg0_dispatch() -> Option { // before creating any threads/the Tokio runtime. load_dotenv(); - // Retain the TempDir so it exists for the lifetime of the invocation of - // this executable. Admittedly, we could invoke `keep()` on it, but it - // would be nice to avoid leaving temporary directories behind, if possible. - match prepend_path_entry_for_codex_aliases() { Ok(path_entry) => Some(path_entry), Err(err) => { @@ -92,6 +88,9 @@ where F: FnOnce(Option) -> Fut, Fut: Future>, { + // Retain the TempDir so it exists for the lifetime of the invocation of + // this executable. Admittedly, we could invoke `keep()` on it, but it + // would be nice to avoid leaving temporary directories behind, if possible. let _path_entry = arg0_dispatch(); // Regular invocation – create a Tokio runtime and execute the provided diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index dd0a3dd4be..6839504be5 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -32,7 +32,7 @@ codex-utils-pty = { workspace = true } codex-utils-readiness = { workspace = true } codex-utils-string = { workspace = true } codex-utils-tokenizer = { workspace = true } -codex_windows_sandbox = { package = "codex-windows-sandbox", path = "../windows-sandbox-rs" } +codex-windows-sandbox = { package = "codex-windows-sandbox", path = "../windows-sandbox-rs" } dirs = { workspace = true } dunce = { workspace = true } env-flags = { workspace = true } diff --git a/codex-rs/utils/pty/src/lib.rs b/codex-rs/utils/pty/src/lib.rs index c91b6b0a2b..14cc430760 100644 --- a/codex-rs/utils/pty/src/lib.rs +++ b/codex-rs/utils/pty/src/lib.rs @@ -134,6 +134,7 @@ pub async fn spawn_pty_process( for (key, value) in env { command_builder.env(key, value); } + let mut child = pair.slave.spawn_command(command_builder)?; let killer = child.clone_killer();