Skip to content

Commit 7ea0985

Browse files
committed
feat: implement zsh shell tool via shell-escalation
1 parent 6b19abf commit 7ea0985

22 files changed

Lines changed: 511 additions & 738 deletions

File tree

codex-rs/Cargo.lock

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/app-server/src/main.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,6 @@ struct AppServerArgs {
2424

2525
fn main() -> anyhow::Result<()> {
2626
arg0_dispatch_or_else(|codex_linux_sandbox_exe| async move {
27-
// Run wrapper mode only after arg0 dispatch so `codex-linux-sandbox`
28-
// invocations don't get misclassified as zsh exec-wrapper calls.
29-
if codex_core::maybe_run_zsh_exec_wrapper_mode()? {
30-
return Ok(());
31-
}
3227
let args = AppServerArgs::parse();
3328
let managed_config_path = managed_config_path_from_debug_env();
3429
let loader_overrides = LoaderOverrides {

codex-rs/arg0/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,6 @@ codex-utils-home-dir = { workspace = true }
1919
dotenvy = { workspace = true }
2020
tempfile = { workspace = true }
2121
tokio = { workspace = true, features = ["rt-multi-thread"] }
22+
23+
[target.'cfg(unix)'.dependencies]
24+
codex-shell-escalation = { workspace = true }

codex-rs/arg0/src/lib.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@ use std::path::PathBuf;
66
use codex_apply_patch::CODEX_CORE_APPLY_PATCH_ARG1;
77
use codex_utils_home_dir::find_codex_home;
88
#[cfg(unix)]
9+
use codex_shell_escalation;
10+
#[cfg(unix)]
911
use std::os::unix::fs::symlink;
1012
use tempfile::TempDir;
1113

1214
const LINUX_SANDBOX_ARG0: &str = "codex-linux-sandbox";
1315
const APPLY_PATCH_ARG0: &str = "apply_patch";
1416
const MISSPELLED_APPLY_PATCH_ARG0: &str = "applypatch";
17+
#[cfg(unix)]
18+
const EXECVE_WRAPPER_ARG0: &str = "codex-execve-wrapper";
1519
const LOCK_FILENAME: &str = ".lock";
1620
const TOKIO_WORKER_STACK_SIZE_BYTES: usize = 16 * 1024 * 1024;
1721

@@ -39,6 +43,22 @@ pub fn arg0_dispatch() -> Option<Arg0PathEntryGuard> {
3943
.and_then(|s| s.to_str())
4044
.unwrap_or("");
4145

46+
#[cfg(unix)]
47+
if exe_name == EXECVE_WRAPPER_ARG0 {
48+
let mut args = std::env::args();
49+
let _ = args.next();
50+
let file = match args.next().and_then(|arg| arg.into_string().ok()) {
51+
Some(file) => file,
52+
None => std::process::exit(1),
53+
};
54+
let argv = args.map(|arg| arg.to_string()).collect::<Vec<_>>();
55+
56+
match codex_shell_escalation::run(file, argv) {
57+
Ok(exit_code) => std::process::exit(exit_code),
58+
Err(_) => std::process::exit(1),
59+
}
60+
}
61+
4262
if exe_name == LINUX_SANDBOX_ARG0 {
4363
// Safety: [`run_main`] never returns.
4464
codex_linux_sandbox::run_main();
@@ -227,6 +247,8 @@ pub fn prepend_path_entry_for_codex_aliases() -> std::io::Result<Arg0PathEntryGu
227247
MISSPELLED_APPLY_PATCH_ARG0,
228248
#[cfg(target_os = "linux")]
229249
LINUX_SANDBOX_ARG0,
250+
#[cfg(unix)]
251+
EXECVE_WRAPPER_ARG0,
230252
] {
231253
let exe = std::env::current_exe()?;
232254

codex-rs/cli/src/main.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -544,11 +544,6 @@ fn stage_str(stage: codex_core::features::Stage) -> &'static str {
544544

545545
fn main() -> anyhow::Result<()> {
546546
arg0_dispatch_or_else(|codex_linux_sandbox_exe| async move {
547-
// Run wrapper mode only after arg0 dispatch so `codex-linux-sandbox`
548-
// invocations don't get misclassified as zsh exec-wrapper calls.
549-
if codex_core::maybe_run_zsh_exec_wrapper_mode()? {
550-
return Ok(());
551-
}
552547
cli_main(codex_linux_sandbox_exe).await?;
553548
Ok(())
554549
})

codex-rs/core/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ windows-sys = { version = "0.52", features = [
138138
[target.'cfg(any(target_os = "freebsd", target_os = "openbsd"))'.dependencies]
139139
keyring = { workspace = true, features = ["sync-secret-service"] }
140140

141+
[target.'cfg(unix)'.dependencies]
142+
codex-shell-escalation = { workspace = true }
143+
141144
[dev-dependencies]
142145
assert_cmd = { workspace = true }
143146
assert_matches = { workspace = true }

codex-rs/core/src/codex.rs

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,6 @@ use crate::turn_diff_tracker::TurnDiffTracker;
249249
use crate::unified_exec::UnifiedExecProcessManager;
250250
use crate::util::backoff;
251251
use crate::windows_sandbox::WindowsSandboxLevelExt;
252-
use crate::zsh_exec_bridge::ZshExecBridge;
253252
use codex_async_utils::OrCancelExt;
254253
use codex_otel::OtelManager;
255254
use codex_otel::TelemetryAuthMode;
@@ -1208,7 +1207,8 @@ impl Session {
12081207
"zsh fork feature enabled, but `zsh_path` is not configured; set `zsh_path` in config.toml"
12091208
)
12101209
})?;
1211-
shell::get_shell(shell::ShellType::Zsh, Some(zsh_path)).ok_or_else(|| {
1210+
let zsh_path = zsh_path.to_path_buf();
1211+
shell::get_shell(shell::ShellType::Zsh, Some(&zsh_path)).ok_or_else(|| {
12121212
anyhow::anyhow!(
12131213
"zsh fork feature enabled, but zsh_path `{}` is not usable; set `zsh_path` to a valid zsh executable",
12141214
zsh_path.display()
@@ -1287,12 +1287,6 @@ impl Session {
12871287
(None, None)
12881288
};
12891289

1290-
let zsh_exec_bridge =
1291-
ZshExecBridge::new(config.zsh_path.clone(), config.codex_home.clone());
1292-
zsh_exec_bridge
1293-
.initialize_for_session(&conversation_id.to_string())
1294-
.await;
1295-
12961290
let services = SessionServices {
12971291
// Initialize the MCP connection manager with an uninitialized
12981292
// instance. It will be replaced with one created via
@@ -1308,7 +1302,7 @@ impl Session {
13081302
unified_exec_manager: UnifiedExecProcessManager::new(
13091303
config.background_terminal_max_timeout,
13101304
),
1311-
zsh_exec_bridge,
1305+
shell_zsh_path: config.zsh_path.clone(),
13121306
analytics_events_client: AnalyticsEventsClient::new(
13131307
Arc::clone(&config),
13141308
Arc::clone(&auth_manager),
@@ -4227,7 +4221,6 @@ mod handlers {
42274221
.unified_exec_manager
42284222
.terminate_all_processes()
42294223
.await;
4230-
sess.services.zsh_exec_bridge.shutdown().await;
42314224
info!("Shutting down Codex instance");
42324225
let history = sess.clone_history().await;
42334226
let turn_count = history
@@ -7895,7 +7888,7 @@ mod tests {
78957888
unified_exec_manager: UnifiedExecProcessManager::new(
78967889
config.background_terminal_max_timeout,
78977890
),
7898-
zsh_exec_bridge: ZshExecBridge::default(),
7891+
shell_zsh_path: None,
78997892
analytics_events_client: AnalyticsEventsClient::new(
79007893
Arc::clone(&config),
79017894
Arc::clone(&auth_manager),
@@ -8048,7 +8041,7 @@ mod tests {
80488041
unified_exec_manager: UnifiedExecProcessManager::new(
80498042
config.background_terminal_max_timeout,
80508043
),
8051-
zsh_exec_bridge: ZshExecBridge::default(),
8044+
shell_zsh_path: None,
80528045
analytics_events_client: AnalyticsEventsClient::new(
80538046
Arc::clone(&config),
80548047
Arc::clone(&auth_manager),

codex-rs/core/src/config/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ pub struct Config {
372372
pub js_repl_node_module_dirs: Vec<PathBuf>,
373373

374374
/// Optional absolute path to patched zsh used by zsh-exec-bridge-backed shell execution.
375-
pub zsh_path: Option<PathBuf>,
375+
pub zsh_path: Option<AbsolutePathBuf>,
376376

377377
/// Value to use for `reasoning.effort` when making a request using the
378378
/// Responses API.
@@ -1484,7 +1484,7 @@ pub struct ConfigOverrides {
14841484
pub codex_linux_sandbox_exe: Option<PathBuf>,
14851485
pub js_repl_node_path: Option<PathBuf>,
14861486
pub js_repl_node_module_dirs: Option<Vec<PathBuf>>,
1487-
pub zsh_path: Option<PathBuf>,
1487+
pub zsh_path: Option<AbsolutePathBuf>,
14881488
pub base_instructions: Option<String>,
14891489
pub developer_instructions: Option<String>,
14901490
pub personality: Option<Personality>,

codex-rs/core/src/lib.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@ pub mod terminal;
109109
mod tools;
110110
pub mod turn_diff_tracker;
111111
mod turn_metadata;
112-
mod zsh_exec_bridge;
113112
pub use rollout::ARCHIVED_SESSIONS_SUBDIR;
114113
pub use rollout::INTERACTIVE_SESSION_SOURCES;
115114
pub use rollout::RolloutRecorder;
@@ -153,8 +152,6 @@ pub use file_watcher::FileWatcherEvent;
153152
pub use safety::get_platform_sandbox;
154153
pub use tools::spec::parse_tool_input_schema;
155154
pub use turn_metadata::build_turn_metadata_header;
156-
pub use zsh_exec_bridge::maybe_run_zsh_exec_wrapper_mode;
157-
158155
pub use client::ModelClient;
159156
pub use client::ModelClientSession;
160157
pub use client::ResponsesWebsocketVersion;

codex-rs/core/src/sandboxing/mod.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -163,19 +163,13 @@ impl SandboxManager {
163163
SandboxType::MacosSeatbelt => {
164164
let mut seatbelt_env = HashMap::new();
165165
seatbelt_env.insert(CODEX_SANDBOX_ENV_VAR.to_string(), "seatbelt".to_string());
166-
let zsh_exec_bridge_wrapper_socket = env
167-
.get(crate::zsh_exec_bridge::ZSH_EXEC_BRIDGE_WRAPPER_SOCKET_ENV_VAR)
168-
.map(PathBuf::from);
169-
let zsh_exec_bridge_allowed_unix_sockets = zsh_exec_bridge_wrapper_socket
170-
.as_ref()
171-
.map_or_else(Vec::new, |path| vec![path.clone()]);
172166
let mut args = create_seatbelt_command_args(
173167
command.clone(),
174168
policy,
175169
sandbox_policy_cwd,
176170
enforce_managed_network,
177171
network,
178-
&zsh_exec_bridge_allowed_unix_sockets,
172+
&[],
179173
);
180174
let mut full_command = Vec::with_capacity(1 + args.len());
181175
full_command.push(MACOS_PATH_TO_SEATBELT_EXECUTABLE.to_string());

0 commit comments

Comments
 (0)