diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 305275ba71..3403f5d69a 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -588,8 +588,11 @@ dependencies = [ "codex-login", "codex-mcp-server", "codex-tui", + "serde", "serde_json", + "tempfile", "tokio", + "toml", "tracing", "tracing-subscriber", "uuid", diff --git a/codex-rs/cli/Cargo.toml b/codex-rs/cli/Cargo.toml index 30e4f521e4..03fd286904 100644 --- a/codex-rs/cli/Cargo.toml +++ b/codex-rs/cli/Cargo.toml @@ -25,6 +25,8 @@ codex-linux-sandbox = { path = "../linux-sandbox" } codex-mcp-server = { path = "../mcp-server" } codex-tui = { path = "../tui" } serde_json = "1" +toml = "0.8" +serde = "1" tokio = { version = "1", features = [ "io-std", "macros", @@ -35,3 +37,6 @@ tokio = { version = "1", features = [ tracing = "0.1.41" tracing-subscriber = "0.3.19" uuid = { version = "1", features = ["serde", "v4"] } + +[dev-dependencies] +tempfile = "3" diff --git a/codex-rs/cli/tests/config_cmd.rs b/codex-rs/cli/tests/config_cmd.rs new file mode 100644 index 0000000000..b737e0f58f --- /dev/null +++ b/codex-rs/cli/tests/config_cmd.rs @@ -0,0 +1,43 @@ +/// Integration test for the `codex config` subcommand. +/// This uses `CARGO_BIN_EXE_codex` to locate the compiled binary. +#[cfg(test)] +mod cli_config { + use std::process::Command; + use std::fs; + use tempfile; + use toml; + + #[test] + fn config_subcommand_help() { + let exe = env!("CARGO_BIN_EXE_codex"); + let output = Command::new(exe) + .arg("config") + .arg("--help") + .output() + .expect("failed to run codex config --help"); + assert!(output.status.success(), "Exited with {:?}", output.status); + let stdout = String::from_utf8_lossy(&output.stdout); + // Should show config subcommands help + assert!(stdout.contains("edit"), "help missing 'edit': {}", stdout); + assert!(stdout.contains("set"), "help missing 'set': {}", stdout); + } + + #[test] + fn config_set_and_read() { + let exe = env!("CARGO_BIN_EXE_codex"); + let tmp = tempfile::tempdir().expect("tempdir"); + let cfg_path = tmp.path().join("config.toml"); + let status = Command::new(exe) + .env("CODEX_HOME", tmp.path()) + .arg("config") + .arg("set") + .arg("tui.auto_mount_repo") + .arg("true") + .status() + .expect("failed to run codex config set"); + assert!(status.success()); + let contents = fs::read_to_string(&cfg_path).expect("read config"); + let doc: toml::Value = toml::from_str(&contents).expect("parse config.toml"); + assert_eq!(doc["tui"]["auto_mount_repo"].as_bool(), Some(true)); + } +} diff --git a/codex-rs/core/src/config.rs b/codex-rs/core/src/config.rs index 74798129ba..1491f30716 100644 --- a/codex-rs/core/src/config.rs +++ b/codex-rs/core/src/config.rs @@ -493,7 +493,7 @@ fn default_model() -> String { /// function will Err if the path does not exist. /// - If `CODEX_HOME` is not set, this function does not verify that the /// directory exists. -fn find_codex_home() -> std::io::Result { +pub fn find_codex_home() -> std::io::Result { // Honor the `CODEX_HOME` environment variable when it is set to allow users // (and tests) to override the default location. if let Ok(val) = std::env::var("CODEX_HOME") { diff --git a/codex-rs/linux-sandbox/src/linux_run_main.rs b/codex-rs/linux-sandbox/src/linux_run_main.rs index a8c73aa75d..7e68bf9eb0 100644 --- a/codex-rs/linux-sandbox/src/linux_run_main.rs +++ b/codex-rs/linux-sandbox/src/linux_run_main.rs @@ -2,7 +2,11 @@ use clap::Parser; use codex_common::SandboxPermissionOption; use std::ffi::CString; +use libc; + use crate::landlock::apply_sandbox_policy_to_current_thread; +use codex_core::config::{Config, ConfigOverrides}; +use codex_core::util::{find_git_root, relative_path_from_git_root}; #[derive(Debug, Parser)] pub struct LandlockCommand { @@ -22,12 +26,48 @@ pub fn run_main() -> ! { None => codex_core::protocol::SandboxPolicy::new_read_only_policy(), }; - let cwd = match std::env::current_dir() { + // Determine working directory inside the session, possibly auto-mounting the repo. + let mut cwd = match std::env::current_dir() { Ok(cwd) => cwd, - Err(e) => { - panic!("failed to getcwd(): {e:?}"); - } + Err(e) => panic!("failed to getcwd(): {e:?}"), }; + // Load configuration to check auto_mount_repo flag + let config = match codex_core::config::Config::load_with_cli_overrides( + Vec::new(), + codex_core::config::ConfigOverrides::default(), + ) { + Ok(cfg) => cfg, + Err(e) => panic!("failed to load config for auto-mount: {e:?}"), + }; + if config.tui.auto_mount_repo { + if let Some(root) = codex_core::util::find_git_root(&cwd) { + // Compute relative subpath + let rel = codex_core::util::relative_path_from_git_root(&cwd).unwrap_or_default(); + let mount_prefix = std::path::PathBuf::from(&config.tui.mount_prefix); + // Create mount target + std::fs::create_dir_all(&mount_prefix).unwrap_or_else(|e| { + panic!("failed to create mount prefix {mount_prefix:?}: {e:?}") + }); + // Bind-mount repository root into session + let src = std::ffi::CString::new(root.to_string_lossy().as_ref()) + .expect("invalid git root path"); + let dst = std::ffi::CString::new(mount_prefix.to_string_lossy().as_ref()) + .expect("invalid mount prefix path"); + unsafe { + libc::mount( + src.as_ptr(), + dst.as_ptr(), + std::ptr::null(), + libc::MS_BIND, + std::ptr::null(), + ); + } + // Change working directory to corresponding subfolder under mount + cwd = mount_prefix.join(rel); + std::env::set_current_dir(&cwd) + .unwrap_or_else(|e| panic!("failed to chdir to {cwd:?}: {e:?}")); + } + } if let Err(e) = apply_sandbox_policy_to_current_thread(&sandbox_policy, &cwd) { panic!("error running landlock: {e:?}");