fix: move arg0 handling out of codex-linux-sandbox and into its own crate (#1697)

This commit is contained in:
Michael Bolin
2025-07-28 08:31:24 -07:00
committed by GitHub
parent 7ecd3153a8
commit 9102255854
14 changed files with 121 additions and 85 deletions

20
codex-rs/Cargo.lock generated
View File

@@ -605,6 +605,17 @@ dependencies = [
"tree-sitter-bash", "tree-sitter-bash",
] ]
[[package]]
name = "codex-arg0"
version = "0.0.0"
dependencies = [
"anyhow",
"codex-core",
"codex-linux-sandbox",
"dotenvy",
"tokio",
]
[[package]] [[package]]
name = "codex-chatgpt" name = "codex-chatgpt"
version = "0.0.0" version = "0.0.0"
@@ -628,11 +639,11 @@ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
"clap_complete", "clap_complete",
"codex-arg0",
"codex-chatgpt", "codex-chatgpt",
"codex-common", "codex-common",
"codex-core", "codex-core",
"codex-exec", "codex-exec",
"codex-linux-sandbox",
"codex-login", "codex-login",
"codex-mcp-server", "codex-mcp-server",
"codex-tui", "codex-tui",
@@ -709,9 +720,9 @@ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
"clap", "clap",
"codex-arg0",
"codex-common", "codex-common",
"codex-core", "codex-core",
"codex-linux-sandbox",
"owo-colors", "owo-colors",
"serde_json", "serde_json",
"shlex", "shlex",
@@ -761,7 +772,6 @@ dependencies = [
"clap", "clap",
"codex-common", "codex-common",
"codex-core", "codex-core",
"dotenvy",
"landlock", "landlock",
"libc", "libc",
"seccompiler", "seccompiler",
@@ -799,8 +809,8 @@ version = "0.0.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"assert_cmd", "assert_cmd",
"codex-arg0",
"codex-core", "codex-core",
"codex-linux-sandbox",
"mcp-types", "mcp-types",
"mcp_test_support", "mcp_test_support",
"pretty_assertions", "pretty_assertions",
@@ -826,10 +836,10 @@ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"clap", "clap",
"codex-ansi-escape", "codex-ansi-escape",
"codex-arg0",
"codex-common", "codex-common",
"codex-core", "codex-core",
"codex-file-search", "codex-file-search",
"codex-linux-sandbox",
"codex-login", "codex-login",
"color-eyre", "color-eyre",
"crossterm", "crossterm",

View File

@@ -3,6 +3,7 @@ resolver = "2"
members = [ members = [
"ansi-escape", "ansi-escape",
"apply-patch", "apply-patch",
"arg0",
"cli", "cli",
"common", "common",
"core", "core",

18
codex-rs/arg0/Cargo.toml Normal file
View File

@@ -0,0 +1,18 @@
[package]
name = "codex-arg0"
version = { workspace = true }
edition = "2024"
[lib]
name = "codex_arg0"
path = "src/lib.rs"
[lints]
workspace = true
[dependencies]
anyhow = "1"
codex-core = { path = "../core" }
codex-linux-sandbox = { path = "../linux-sandbox" }
dotenvy = "0.15.7"
tokio = { version = "1", features = ["rt-multi-thread"] }

68
codex-rs/arg0/src/lib.rs Normal file
View File

@@ -0,0 +1,68 @@
use std::future::Future;
use std::path::Path;
use std::path::PathBuf;
/// 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. Use [`dotenvy::from_path`] and [`dotenvy::dotenv`] to modify the
/// environment 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<PathBuf>`, 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<F, Fut>(main_fn: F) -> anyhow::Result<()>
where
F: FnOnce(Option<PathBuf>) -> Fut,
Fut: Future<Output = anyhow::Result<()>>,
{
// Determine if we were invoked via the special alias.
let argv0 = std::env::args().next().unwrap_or_default();
let exe_name = Path::new(&argv0)
.file_name()
.and_then(|s| s.to_str())
.unwrap_or("");
if exe_name == "codex-linux-sandbox" {
// Safety: [`run_main`] never returns.
codex_linux_sandbox::run_main();
}
// This modifies the environment, which is not thread-safe, so do this
// before creating any threads/the Tokio runtime.
load_dotenv();
// Regular invocation create a Tokio runtime and execute the provided
// async entry-point.
let runtime = tokio::runtime::Runtime::new()?;
runtime.block_on(async move {
let codex_linux_sandbox_exe: Option<PathBuf> = if cfg!(target_os = "linux") {
std::env::current_exe().ok()
} else {
None
};
main_fn(codex_linux_sandbox_exe).await
})
}
/// Load env vars from ~/.codex/.env and `$(pwd)/.env`.
fn load_dotenv() {
if let Ok(codex_home) = codex_core::config::find_codex_home() {
dotenvy::from_path(codex_home.join(".env")).ok();
}
dotenvy::dotenv().ok();
}

View File

@@ -18,12 +18,12 @@ workspace = true
anyhow = "1" anyhow = "1"
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive"] }
clap_complete = "4" clap_complete = "4"
codex-arg0 = { path = "../arg0" }
codex-chatgpt = { path = "../chatgpt" } codex-chatgpt = { path = "../chatgpt" }
codex-core = { path = "../core" } codex-core = { path = "../core" }
codex-common = { path = "../common", features = ["cli"] } codex-common = { path = "../common", features = ["cli"] }
codex-exec = { path = "../exec" } codex-exec = { path = "../exec" }
codex-login = { path = "../login" } codex-login = { path = "../login" }
codex-linux-sandbox = { path = "../linux-sandbox" }
codex-mcp-server = { path = "../mcp-server" } codex-mcp-server = { path = "../mcp-server" }
codex-tui = { path = "../tui" } codex-tui = { path = "../tui" }
serde_json = "1" serde_json = "1"

View File

@@ -2,6 +2,7 @@ use clap::CommandFactory;
use clap::Parser; use clap::Parser;
use clap_complete::Shell; use clap_complete::Shell;
use clap_complete::generate; use clap_complete::generate;
use codex_arg0::arg0_dispatch_or_else;
use codex_chatgpt::apply_command::ApplyCommand; use codex_chatgpt::apply_command::ApplyCommand;
use codex_chatgpt::apply_command::run_apply_command; use codex_chatgpt::apply_command::run_apply_command;
use codex_cli::LandlockCommand; use codex_cli::LandlockCommand;
@@ -92,7 +93,7 @@ struct LoginCommand {
} }
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
codex_linux_sandbox::run_with_sandbox(|codex_linux_sandbox_exe| async move { arg0_dispatch_or_else(|codex_linux_sandbox_exe| async move {
cli_main(codex_linux_sandbox_exe).await?; cli_main(codex_linux_sandbox_exe).await?;
Ok(()) Ok(())
}) })

View File

@@ -18,13 +18,13 @@ workspace = true
anyhow = "1" anyhow = "1"
chrono = "0.4.40" chrono = "0.4.40"
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive"] }
codex-arg0 = { path = "../arg0" }
codex-core = { path = "../core" } codex-core = { path = "../core" }
codex-common = { path = "../common", features = [ codex-common = { path = "../common", features = [
"cli", "cli",
"elapsed", "elapsed",
"sandbox_summary", "sandbox_summary",
] } ] }
codex-linux-sandbox = { path = "../linux-sandbox" }
owo-colors = "4.2.0" owo-colors = "4.2.0"
serde_json = "1" serde_json = "1"
shlex = "1.3.0" shlex = "1.3.0"

View File

@@ -10,6 +10,7 @@
//! This allows us to ship a completely separate set of functionality as part //! This allows us to ship a completely separate set of functionality as part
//! of the `codex-exec` binary. //! of the `codex-exec` binary.
use clap::Parser; use clap::Parser;
use codex_arg0::arg0_dispatch_or_else;
use codex_common::CliConfigOverrides; use codex_common::CliConfigOverrides;
use codex_exec::Cli; use codex_exec::Cli;
use codex_exec::run_main; use codex_exec::run_main;
@@ -24,7 +25,7 @@ struct TopCli {
} }
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
codex_linux_sandbox::run_with_sandbox(|codex_linux_sandbox_exe| async move { arg0_dispatch_or_else(|codex_linux_sandbox_exe| async move {
let top_cli = TopCli::parse(); let top_cli = TopCli::parse();
// Merge root-level overrides into inner CLI struct so downstream logic remains unchanged. // Merge root-level overrides into inner CLI struct so downstream logic remains unchanged.
let mut inner = top_cli.inner; let mut inner = top_cli.inner;

View File

@@ -14,15 +14,16 @@ path = "src/lib.rs"
[lints] [lints]
workspace = true workspace = true
[dependencies] [target.'cfg(target_os = "linux")'.dependencies]
anyhow = "1" anyhow = "1"
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive"] }
codex-common = { path = "../common", features = ["cli"] } codex-common = { path = "../common", features = ["cli"] }
codex-core = { path = "../core" } codex-core = { path = "../core" }
dotenvy = "0.15.7" libc = "0.2.172"
tokio = { version = "1", features = ["rt-multi-thread"] } landlock = "0.4.1"
seccompiler = "0.5.0"
[dev-dependencies] [target.'cfg(target_os = "linux")'.dev-dependencies]
tempfile = "3" tempfile = "3"
tokio = { version = "1", features = [ tokio = { version = "1", features = [
"io-std", "io-std",
@@ -31,8 +32,3 @@ tokio = { version = "1", features = [
"rt-multi-thread", "rt-multi-thread",
"signal", "signal",
] } ] }
[target.'cfg(target_os = "linux")'.dependencies]
libc = "0.2.172"
landlock = "0.4.1"
seccompiler = "0.5.0"

View File

@@ -4,72 +4,11 @@ mod landlock;
mod linux_run_main; mod linux_run_main;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub use linux_run_main::run_main; pub fn run_main() -> ! {
linux_run_main::run_main();
use std::future::Future;
use std::path::PathBuf;
/// Helper that consolidates the common boilerplate found in several Codex
/// binaries (`codex`, `codex-exec`, `codex-tui`) around dispatching to the
/// `codex-linux-sandbox` sub-command.
///
/// When the current executable is invoked through the hard-link or alias
/// named `codex-linux-sandbox` we *directly* execute [`run_main`](crate::run_main)
/// (which never returns). Otherwise we:
/// 1. Construct a Tokio multi-thread runtime.
/// 2. Derive the path to the current executable (so children can re-invoke
/// the sandbox) when running on Linux.
/// 3. Execute the provided async `main_fn` inside that runtime, forwarding
/// any error.
///
/// This function eliminates duplicated code across the various `main.rs`
/// entry-points.
pub fn run_with_sandbox<F, Fut>(main_fn: F) -> anyhow::Result<()>
where
F: FnOnce(Option<PathBuf>) -> Fut,
Fut: Future<Output = anyhow::Result<()>>,
{
use std::path::Path;
// Determine if we were invoked via the special alias.
let argv0 = std::env::args().next().unwrap_or_default();
let exe_name = Path::new(&argv0)
.file_name()
.and_then(|s| s.to_str())
.unwrap_or("");
if exe_name == "codex-linux-sandbox" {
// Safety: [`run_main`] never returns.
crate::run_main();
}
// This modifies the environment, which is not thread-safe, so do this
// before creating any threads/the Tokio runtime.
load_dotenv();
// Regular invocation create a Tokio runtime and execute the provided
// async entry-point.
let runtime = tokio::runtime::Runtime::new()?;
runtime.block_on(async move {
let codex_linux_sandbox_exe: Option<PathBuf> = if cfg!(target_os = "linux") {
std::env::current_exe().ok()
} else {
None
};
main_fn(codex_linux_sandbox_exe).await
})
} }
#[cfg(not(target_os = "linux"))] #[cfg(not(target_os = "linux"))]
pub fn run_main() -> ! { pub fn run_main() -> ! {
panic!("codex-linux-sandbox is only supported on Linux"); panic!("codex-linux-sandbox is only supported on Linux");
} }
/// Load env vars from ~/.codex/.env and `$(pwd)/.env`.
fn load_dotenv() {
if let Ok(codex_home) = codex_core::config::find_codex_home() {
dotenvy::from_path(codex_home.join(".env")).ok();
}
dotenvy::dotenv().ok();
}

View File

@@ -16,8 +16,8 @@ workspace = true
[dependencies] [dependencies]
anyhow = "1" anyhow = "1"
codex-arg0 = { path = "../arg0" }
codex-core = { path = "../core" } codex-core = { path = "../core" }
codex-linux-sandbox = { path = "../linux-sandbox" }
mcp-types = { path = "../mcp-types" } mcp-types = { path = "../mcp-types" }
schemars = "0.8.22" schemars = "0.8.22"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }

View File

@@ -1,7 +1,8 @@
use codex_arg0::arg0_dispatch_or_else;
use codex_mcp_server::run_main; use codex_mcp_server::run_main;
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
codex_linux_sandbox::run_with_sandbox(|codex_linux_sandbox_exe| async move { arg0_dispatch_or_else(|codex_linux_sandbox_exe| async move {
run_main(codex_linux_sandbox_exe).await?; run_main(codex_linux_sandbox_exe).await?;
Ok(()) Ok(())
}) })

View File

@@ -19,6 +19,7 @@ anyhow = "1"
base64 = "0.22.1" base64 = "0.22.1"
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive"] }
codex-ansi-escape = { path = "../ansi-escape" } codex-ansi-escape = { path = "../ansi-escape" }
codex-arg0 = { path = "../arg0" }
codex-core = { path = "../core" } codex-core = { path = "../core" }
codex-common = { path = "../common", features = [ codex-common = { path = "../common", features = [
"cli", "cli",
@@ -26,7 +27,6 @@ codex-common = { path = "../common", features = [
"sandbox_summary", "sandbox_summary",
] } ] }
codex-file-search = { path = "../file-search" } codex-file-search = { path = "../file-search" }
codex-linux-sandbox = { path = "../linux-sandbox" }
codex-login = { path = "../login" } codex-login = { path = "../login" }
color-eyre = "0.6.3" color-eyre = "0.6.3"
crossterm = { version = "0.28.1", features = ["bracketed-paste"] } crossterm = { version = "0.28.1", features = ["bracketed-paste"] }

View File

@@ -1,4 +1,5 @@
use clap::Parser; use clap::Parser;
use codex_arg0::arg0_dispatch_or_else;
use codex_common::CliConfigOverrides; use codex_common::CliConfigOverrides;
use codex_tui::Cli; use codex_tui::Cli;
use codex_tui::run_main; use codex_tui::run_main;
@@ -13,7 +14,7 @@ struct TopCli {
} }
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
codex_linux_sandbox::run_with_sandbox(|codex_linux_sandbox_exe| async move { arg0_dispatch_or_else(|codex_linux_sandbox_exe| async move {
let top_cli = TopCli::parse(); let top_cli = TopCli::parse();
let mut inner = top_cli.inner; let mut inner = top_cli.inner;
inner inner