Standardize shell execution on unified exec (#39772)

## What changed

- Use `exec_command` and `write_stdin` as the shell tool surface.
- Treat legacy `default`, `local`, and `shell_command` model metadata as `unified_exec`.
- Remove obsolete shell-selection configuration and runtime paths while preserving the feature and policy gates for zsh fork execution.

GitOrigin-RevId: d743cbe598630d73052f1fecad680c4cde17977d
This commit is contained in:
jif authored and copyberry committed 2026-08-20 18:29:35 +00:00
1 parent 5bcd7b0fbc
commit bce5f2fcfc
40 files changed
+189 -992

No files matched your search

+29 -221
View File
@@ -9,7 +9,6 @@ use std::sync::OnceLock;
use anyhow::Context;
use anyhow::Result;
use codex_exec_server::CreateDirectoryOptions;
use codex_features::Feature;
use codex_protocol::config_types::CollaborationMode;
use codex_protocol::config_types::EnvironmentVariablePattern;
use codex_protocol::config_types::ModeKind;
@@ -259,11 +258,6 @@ async fn exec_command_hides_and_rejects_login_when_disabled() -> Result<()> {
let builder = test_codex().with_model("gpt-5.4").with_config(|config| {
config.permissions.allow_login_shell = false;
config.use_experimental_unified_exec_tool = true;
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let harness = TestCodexHarness::with_builder(builder).await?;
let call_id = "exec-command-login-disabled";
@@ -369,11 +363,6 @@ async fn exec_command_uses_installed_environment_shell_policy_with_explicit_over
skip_if_no_network!(Ok(()));
let builder = test_codex().with_model("gpt-5.4").with_config(|config| {
config.use_experimental_unified_exec_tool = true;
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
config.permissions.shell_environment_policy = ShellEnvironmentPolicy {
inherit: ShellEnvironmentPolicyInherit::None,
include_only: vec![EnvironmentVariablePattern::new_case_insensitive("DROP")],
@@ -468,13 +457,7 @@ async fn unified_exec_intercepts_apply_patch_exec_command() -> Result<()> {
skip_if_sandbox!(Ok(()));
skip_if_host_windows!(Ok(()));
let builder = test_codex().with_config(|config| {
config.use_experimental_unified_exec_tool = true;
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let builder = test_codex();
let harness = TestCodexHarness::with_builder(builder).await?;
let patch =
@@ -607,13 +590,7 @@ async fn unified_exec_rejects_justification_without_sandbox_permissions() -> Res
skip_if_no_network!(Ok(()));
let server = start_mock_server().await;
let mut builder = test_codex().with_model("gpt-5.2").with_config(|config| {
config.use_experimental_unified_exec_tool = true;
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex().with_model("gpt-5.2");
let test = builder.build_with_auto_env(&server).await?;
let call_id = "uexec-missing-sandbox-permissions";
@@ -686,13 +663,7 @@ async fn unified_exec_emits_exec_command_begin_event() -> Result<()> {
let server = start_mock_server().await;
let mut builder = test_codex().with_model("gpt-5.2").with_config(|config| {
config.use_experimental_unified_exec_tool = true;
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex().with_model("gpt-5.2");
let test = builder.build_with_auto_env(&server).await?;
let cwd = test.config.cwd.to_path_buf();
@@ -749,13 +720,7 @@ async fn unified_exec_resolves_relative_workdir() -> Result<()> {
let server = start_mock_server().await;
let mut builder = test_codex().with_model("gpt-5.2").with_config(|config| {
config.use_experimental_unified_exec_tool = true;
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex().with_model("gpt-5.2");
let test = builder.build_with_auto_env(&server).await?;
let workdir_rel = std::path::PathBuf::from("uexec_relative_workdir");
@@ -819,13 +784,7 @@ async fn unified_exec_respects_workdir_override() -> Result<()> {
let server = start_mock_server().await;
let mut builder = test_codex().with_model("gpt-5.2").with_config(|config| {
config.use_experimental_unified_exec_tool = true;
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex().with_model("gpt-5.2");
let test = builder.build_with_auto_env(&server).await?;
let workdir = create_workspace_directory(&test, "uexec_workdir_test").await?;
@@ -885,13 +844,7 @@ async fn unified_exec_emits_exec_command_end_event() -> Result<()> {
let server = start_mock_server().await;
let mut builder = test_codex().with_config(|config| {
config.use_experimental_unified_exec_tool = true;
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex();
let test = builder.build_with_auto_env(&server).await?;
let call_id = "uexec-end-event";
@@ -959,13 +912,7 @@ async fn unified_exec_emits_output_delta_for_exec_command() -> Result<()> {
let server = start_mock_server().await;
let mut builder = test_codex().with_config(|config| {
config.use_experimental_unified_exec_tool = true;
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex();
let test = builder.build_with_auto_env(&server).await?;
let call_id = "uexec-delta-1";
@@ -1018,13 +965,7 @@ async fn unified_exec_full_lifecycle_with_background_end_event() -> Result<()> {
let server = start_mock_server().await;
let mut builder = test_codex().with_config(|config| {
config.use_experimental_unified_exec_tool = true;
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex();
let test = builder.build_with_auto_env(&server).await?;
let call_id = "uexec-full-lifecycle";
@@ -1262,11 +1203,6 @@ allow_local_binding = true
.with_home(home)
.with_cloud_config_bundle(managed_network_requirements_loader())
.with_config(move |config| {
config.use_experimental_unified_exec_tool = true;
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
config.permissions.approval_policy = Constrained::allow_any(AskForApproval::Never);
config
.permissions
@@ -1351,13 +1287,7 @@ async fn unified_exec_emits_terminal_interaction_for_write_stdin() -> Result<()>
let server = start_mock_server().await;
let mut builder = test_codex().with_config(|config| {
config.use_experimental_unified_exec_tool = true;
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex();
let test = builder.build_with_auto_env(&server).await?;
let open_call_id = "uexec-open";
@@ -1435,13 +1365,7 @@ async fn unified_exec_terminal_interaction_captures_delayed_output() -> Result<(
let server = start_mock_server().await;
let mut builder = test_codex().with_config(|config| {
config.use_experimental_unified_exec_tool = true;
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex();
let test = builder.build_with_auto_env(&server).await?;
let open_call_id = "uexec-delayed-open";
@@ -1616,13 +1540,7 @@ async fn unified_exec_emits_one_begin_and_one_end_event() -> Result<()> {
let server = start_mock_server().await;
let mut builder = test_codex().with_config(|config| {
config.use_experimental_unified_exec_tool = true;
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex();
let test = builder.build_with_auto_env(&server).await?;
let open_call_id = "uexec-open-session";
@@ -1740,12 +1658,7 @@ async fn exec_command_reports_chunk_and_exit_metadata() -> Result<()> {
let server = start_mock_server().await;
let mut builder = test_codex().with_config(|config| {
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex();
let test = builder.build_with_auto_env(&server).await?;
let call_id = "uexec-metadata";
@@ -1835,12 +1748,7 @@ async fn exec_command_clamps_model_requested_max_output_tokens_to_policy() -> Re
let server = start_mock_server().await;
let mut builder = test_codex().with_model("gpt-5.4").with_config(|config| {
config.use_experimental_unified_exec_tool = true;
config.tool_output_token_limit = Some(50);
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let test = builder.build_with_auto_env(&server).await?;
@@ -1899,12 +1807,7 @@ async fn write_stdin_clamps_model_requested_max_output_tokens_to_policy() -> Res
let server = start_mock_server().await;
let mut builder = test_codex().with_model("gpt-5.4").with_config(|config| {
config.use_experimental_unified_exec_tool = true;
config.tool_output_token_limit = Some(50);
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let test = builder.build_with_auto_env(&server).await?;
@@ -1989,12 +1892,7 @@ async fn unified_exec_defaults_to_pipe() -> Result<()> {
let server = start_mock_server().await;
let mut builder = test_codex().with_config(|config| {
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex();
let test = builder.build_with_auto_env(&server).await?;
let call_id = "uexec-default-pipe";
@@ -2059,12 +1957,7 @@ async fn unified_exec_can_enable_tty() -> Result<()> {
let server = start_mock_server().await;
let mut builder = test_codex().with_config(|config| {
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex();
let test = builder.build_with_auto_env(&server).await?;
let call_id = "uexec-tty-enabled";
@@ -2126,12 +2019,7 @@ async fn unified_exec_respects_early_exit_notifications() -> Result<()> {
let server = start_mock_server().await;
let mut builder = test_codex().with_config(|config| {
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex();
let test = builder.build_with_auto_env(&server).await?;
let call_id = "uexec-early-exit";
@@ -2210,12 +2098,7 @@ async fn write_stdin_returns_exit_metadata_and_clears_session() -> Result<()> {
let server = start_mock_server().await;
let mut builder = test_codex().with_config(|config| {
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex();
let test = builder.build_with_auto_env(&server).await?;
let start_call_id = "uexec-cat-start";
@@ -2418,12 +2301,7 @@ async fn assert_write_stdin_ctrl_c_interrupts_non_tty_session(
let server = start_mock_server().await;
let mut builder = test_codex().with_config(|config| {
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex();
let test = builder.build_with_auto_env(&server).await?;
let start_call_id = format!("uexec-non-tty-interrupt-{test_name}-start");
@@ -2541,12 +2419,7 @@ async fn write_stdin_ctrl_c_terminates_non_tty_session_on_windows() -> Result<()
let server = start_mock_server().await;
let mut builder = test_codex().with_config(|config| {
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex();
let test = builder.build_with_auto_env(&server).await?;
let start_call_id = "uexec-windows-interrupt-start";
@@ -2635,13 +2508,7 @@ async fn unified_exec_emits_end_event_when_session_dies_via_stdin() -> Result<()
let server = start_mock_server().await;
let mut builder = test_codex().with_config(|config| {
config.use_experimental_unified_exec_tool = true;
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex();
let test = builder.build_with_auto_env(&server).await?;
let start_call_id = "uexec-end-on-exit-start";
@@ -2727,13 +2594,7 @@ async fn unified_exec_keeps_long_running_session_after_turn_end() -> Result<()>
let server = start_mock_server().await;
let mut builder = test_codex().with_config(|config| {
config.use_experimental_unified_exec_tool = true;
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex();
let TestCodex {
codex,
cwd,
@@ -2834,13 +2695,7 @@ async fn unified_exec_interrupt_preserves_long_running_session() -> Result<()> {
let server = start_mock_server().await;
let mut builder = test_codex().with_config(|config| {
config.use_experimental_unified_exec_tool = true;
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex();
let TestCodex {
codex,
cwd,
@@ -2930,12 +2785,7 @@ async fn unified_exec_reuses_session_via_stdin() -> Result<()> {
let server = start_mock_server().await;
let mut builder = test_codex().with_config(|config| {
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex();
let test = builder.build_with_auto_env(&server).await?;
let first_call_id = "uexec-start";
@@ -3029,13 +2879,7 @@ async fn unified_exec_streams_after_lagged_output() -> Result<()> {
let server = start_mock_server().await;
let mut builder = test_codex().with_config(|config| {
config.use_experimental_unified_exec_tool = true;
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex();
let test = builder.build_with_auto_env(&server).await?;
let script = r#"python3 - <<'PY'
@@ -3146,12 +2990,7 @@ async fn unified_exec_timeout_and_followup_poll() -> Result<()> {
let server = start_mock_server().await;
let mut builder = test_codex().with_config(|config| {
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex();
let test = builder.build_with_auto_env(&server).await?;
let first_call_id = "uexec-timeout";
@@ -3239,12 +3078,7 @@ async fn unified_exec_formats_large_output_summary() -> Result<()> {
let server = start_mock_server().await;
let mut builder = test_codex().with_config(|config| {
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex();
let test = builder.build_with_auto_env(&server).await?;
let output_line = "token token \n";
@@ -3335,12 +3169,7 @@ async fn unified_exec_runs_under_sandbox() -> Result<()> {
let server = start_mock_server().await;
let mut builder = test_codex().with_config(|config| {
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex();
let TestCodex {
codex,
cwd,
@@ -3428,10 +3257,6 @@ async fn unified_exec_enforces_glob_deny_read_policy() -> Result<()> {
let server = start_mock_server().await;
let mut builder = test_codex().with_config(move |config| {
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
let mut file_system_sandbox_policy = FileSystemSandboxPolicy::default();
file_system_sandbox_policy
.entries
@@ -3567,13 +3392,7 @@ async fn unified_exec_python_prompt_under_seatbelt() -> Result<()> {
let server = start_mock_server().await;
let mut builder = test_codex().with_config(|config| {
config.use_experimental_unified_exec_tool = true;
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex();
let TestCodex {
codex,
cwd,
@@ -3703,12 +3522,7 @@ async fn unified_exec_runs_on_all_platforms() -> Result<()> {
let server = start_mock_server().await;
let mut builder = test_codex().with_config(|config| {
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex();
let test = builder.build_with_auto_env(&server).await?;
let call_id = "uexec";
@@ -3766,13 +3580,7 @@ async fn write_stdin_calls_run_in_parallel_across_sessions() -> Result<()> {
skip_if_target_windows!(Ok(()), "uses bash and POSIX file rendezvous commands");
let server = start_mock_server().await;
let mut builder = test_codex().with_model("gpt-5.4").with_config(|config| {
config.use_experimental_unified_exec_tool = true;
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let mut builder = test_codex().with_model("gpt-5.4");
let test = builder.build_with_auto_env(&server).await?;
let start_args = serde_json::to_string(&json!({