From f4aed0f1be90ed547ea1769c5ac2117a800b3542 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Tue, 17 Mar 2026 10:51:30 -0700 Subject: [PATCH 01/14] Prefer system bwrap on AppArmor hosts --- codex-rs/core/README.md | 5 + codex-rs/linux-sandbox/README.md | 16 +- codex-rs/linux-sandbox/src/launcher.rs | 188 +++++++++++++++++++ codex-rs/linux-sandbox/src/lib.rs | 2 + codex-rs/linux-sandbox/src/linux_run_main.rs | 16 +- 5 files changed, 218 insertions(+), 9 deletions(-) create mode 100644 codex-rs/linux-sandbox/src/launcher.rs diff --git a/codex-rs/core/README.md b/codex-rs/core/README.md index 6fddf2f87cf7..4d9ec91cb112 100644 --- a/codex-rs/core/README.md +++ b/codex-rs/core/README.md @@ -60,6 +60,11 @@ only when the split filesystem policy round-trips through the legacy cases like `/repo = write`, `/repo/a = none`, `/repo/a/b = write`, where the more specific writable child must reopen under a denied parent. +On Ubuntu/AppArmor hosts that ship `/etc/apparmor.d/bwrap-userns-restrict`, +the Linux sandbox helper prefers `/usr/bin/bwrap` so the distro's userns +exception applies. Other Linux hosts continue to use the vendored bubblewrap +path compiled into the helper binary. + ### All Platforms Expects the binary containing `codex-core` to simulate the virtual `apply_patch` CLI when `arg1` is `--codex-run-as-apply-patch`. See the `codex-arg0` crate for details. diff --git a/codex-rs/linux-sandbox/README.md b/codex-rs/linux-sandbox/README.md index b3f0c05b67c7..41e51db40b35 100644 --- a/codex-rs/linux-sandbox/README.md +++ b/codex-rs/linux-sandbox/README.md @@ -7,13 +7,21 @@ This crate is responsible for producing: - the `codex-exec` CLI can check if its arg0 is `codex-linux-sandbox` and, if so, execute as if it were `codex-linux-sandbox` - this should also be true of the `codex` multitool CLI -On Linux, the bubblewrap pipeline uses the vendored bubblewrap path compiled -into this binary. +On Linux, the bubblewrap pipeline prefers the system `/usr/bin/bwrap` when the +Ubuntu AppArmor `bwrap-userns-restrict` profile is installed, because that +profile grants user namespace setup specifically to the system path. Otherwise, +the helper uses the vendored bubblewrap path compiled into this binary. **Current Behavior** - Legacy `SandboxPolicy` / `sandbox_mode` configs remain supported. -- Bubblewrap is the default filesystem sandbox pipeline and is standardized on - the vendored path. +- Bubblewrap is the default filesystem sandbox pipeline. +- On Ubuntu/AppArmor hosts that ship `/etc/apparmor.d/bwrap-userns-restrict`, + the helper prefers `/usr/bin/bwrap` so the distro AppArmor exception still + applies. +- If that AppArmor profile exists but `/usr/bin/bwrap` is missing, the helper + warns on stderr and falls back to the vendored bubblewrap path for now. +- On other Linux hosts, the helper continues to use the vendored bubblewrap + path compiled into the sandbox binary. - Legacy Landlock + mount protections remain available as an explicit legacy fallback path. - Set `features.use_legacy_landlock = true` (or CLI `-c use_legacy_landlock=true`) diff --git a/codex-rs/linux-sandbox/src/launcher.rs b/codex-rs/linux-sandbox/src/launcher.rs new file mode 100644 index 000000000000..2c7095f1ee4f --- /dev/null +++ b/codex-rs/linux-sandbox/src/launcher.rs @@ -0,0 +1,188 @@ +use std::ffi::CString; +use std::fs::File; +use std::os::fd::AsRawFd; +use std::os::raw::c_char; +use std::os::unix::ffi::OsStrExt; +use std::path::Path; +use std::path::PathBuf; + +use crate::vendored_bwrap::exec_vendored_bwrap; + +const SYSTEM_BWRAP_PATH: &str = "/usr/bin/bwrap"; +const UBUNTU_APPARMOR_BWRAP_USERNS_RESTRICT_PROFILE: &str = "/etc/apparmor.d/bwrap-userns-restrict"; + +#[derive(Debug, Clone, PartialEq, Eq)] +enum BubblewrapLauncher { + System(PathBuf), + Vendored { warn_missing_system_bwrap: bool }, +} + +pub(crate) fn exec_bwrap( + argv: Vec, + preserved_files: Vec, + emit_missing_system_bwrap_warning: bool, +) -> ! { + match preferred_bwrap_launcher() { + BubblewrapLauncher::System(program) => exec_system_bwrap(&program, argv, preserved_files), + BubblewrapLauncher::Vendored { + warn_missing_system_bwrap, + } => { + if warn_missing_system_bwrap && emit_missing_system_bwrap_warning { + eprintln!( + "warning: Codex could not find system bubblewrap at {SYSTEM_BWRAP_PATH}. Please install bubblewrap with your package manager. Codex will use the vendored bubblewrap in the meantime." + ); + } + exec_vendored_bwrap(argv, preserved_files) + } + } +} + +fn preferred_bwrap_launcher() -> BubblewrapLauncher { + let apparmor_profile_exists = Path::new(UBUNTU_APPARMOR_BWRAP_USERNS_RESTRICT_PROFILE).exists(); + let system_bwrap_exists = Path::new(SYSTEM_BWRAP_PATH).exists(); + + preferred_bwrap_launcher_with(apparmor_profile_exists, system_bwrap_exists) +} + +fn preferred_bwrap_launcher_with( + apparmor_profile_exists: bool, + system_bwrap_exists: bool, +) -> BubblewrapLauncher { + match (apparmor_profile_exists, system_bwrap_exists) { + (true, true) => BubblewrapLauncher::System(PathBuf::from(SYSTEM_BWRAP_PATH)), + (true, false) => BubblewrapLauncher::Vendored { + warn_missing_system_bwrap: true, + }, + (false, _) => BubblewrapLauncher::Vendored { + warn_missing_system_bwrap: false, + }, + } +} + +fn exec_system_bwrap(program: &Path, argv: Vec, preserved_files: Vec) -> ! { + make_files_inheritable(&preserved_files); + + let program_path = program.display().to_string(); + let program = CString::new(program.as_os_str().as_bytes()) + .unwrap_or_else(|err| panic!("invalid system bubblewrap path: {err}")); + let cstrings = argv_to_cstrings(&argv); + let mut argv_ptrs: Vec<*const c_char> = cstrings.iter().map(|arg| arg.as_ptr()).collect(); + argv_ptrs.push(std::ptr::null()); + + // SAFETY: `program` and every entry in `argv_ptrs` are valid C strings for + // the duration of the call. On success `execv` does not return. + unsafe { + libc::execv(program.as_ptr(), argv_ptrs.as_ptr()); + } + let err = std::io::Error::last_os_error(); + panic!("failed to exec system bubblewrap {program_path}: {err}"); +} + +fn argv_to_cstrings(argv: &[String]) -> Vec { + let mut cstrings: Vec = Vec::with_capacity(argv.len()); + for arg in argv { + match CString::new(arg.as_str()) { + Ok(value) => cstrings.push(value), + Err(err) => panic!("failed to convert argv to CString: {err}"), + } + } + cstrings +} + +fn make_files_inheritable(files: &[File]) { + for file in files { + clear_cloexec(file.as_raw_fd()); + } +} + +fn clear_cloexec(fd: libc::c_int) { + // SAFETY: `fd` is an owned descriptor kept alive by `files`. + let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) }; + if flags < 0 { + let err = std::io::Error::last_os_error(); + panic!("failed to read fd flags for preserved bubblewrap file descriptor {fd}: {err}"); + } + let cleared_flags = flags & !libc::FD_CLOEXEC; + if cleared_flags == flags { + return; + } + + // SAFETY: `fd` is valid and we are only clearing FD_CLOEXEC. + let result = unsafe { libc::fcntl(fd, libc::F_SETFD, cleared_flags) }; + if result < 0 { + let err = std::io::Error::last_os_error(); + panic!("failed to clear CLOEXEC for preserved bubblewrap file descriptor {fd}: {err}"); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + use tempfile::NamedTempFile; + + #[test] + fn prefers_system_bwrap_for_ubuntu_apparmor_profile() { + assert_eq!( + preferred_bwrap_launcher_with( + /*apparmor_profile_exists*/ true, /*system_bwrap_exists*/ true + ), + BubblewrapLauncher::System(PathBuf::from(SYSTEM_BWRAP_PATH)) + ); + } + + #[test] + fn falls_back_to_vendored_without_warning_when_apparmor_profile_is_absent() { + assert_eq!( + preferred_bwrap_launcher_with( + /*apparmor_profile_exists*/ false, /*system_bwrap_exists*/ true + ), + BubblewrapLauncher::Vendored { + warn_missing_system_bwrap: false, + } + ); + } + + #[test] + fn falls_back_to_vendored_with_warning_when_apparmor_profile_exists_but_system_bwrap_is_missing() + { + assert_eq!( + preferred_bwrap_launcher_with( + /*apparmor_profile_exists*/ true, /*system_bwrap_exists*/ false + ), + BubblewrapLauncher::Vendored { + warn_missing_system_bwrap: true, + } + ); + } + + #[test] + fn preserved_files_are_made_inheritable_for_system_exec() { + let file = NamedTempFile::new().expect("temp file"); + set_cloexec(file.as_file().as_raw_fd()); + + make_files_inheritable(std::slice::from_ref(file.as_file())); + + assert_eq!(fd_flags(file.as_file().as_raw_fd()) & libc::FD_CLOEXEC, 0); + } + + fn set_cloexec(fd: libc::c_int) { + let flags = fd_flags(fd); + // SAFETY: `fd` is valid for the duration of the test. + let result = unsafe { libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC) }; + if result < 0 { + let err = std::io::Error::last_os_error(); + panic!("failed to set CLOEXEC for test fd {fd}: {err}"); + } + } + + fn fd_flags(fd: libc::c_int) -> libc::c_int { + // SAFETY: `fd` is valid for the duration of the test. + let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) }; + if flags < 0 { + let err = std::io::Error::last_os_error(); + panic!("failed to read fd flags for test fd {fd}: {err}"); + } + flags + } +} diff --git a/codex-rs/linux-sandbox/src/lib.rs b/codex-rs/linux-sandbox/src/lib.rs index e364c19251d9..900287c99dc4 100644 --- a/codex-rs/linux-sandbox/src/lib.rs +++ b/codex-rs/linux-sandbox/src/lib.rs @@ -8,6 +8,8 @@ mod bwrap; #[cfg(target_os = "linux")] mod landlock; #[cfg(target_os = "linux")] +mod launcher; +#[cfg(target_os = "linux")] mod linux_run_main; #[cfg(target_os = "linux")] mod proxy_routing; diff --git a/codex-rs/linux-sandbox/src/linux_run_main.rs b/codex-rs/linux-sandbox/src/linux_run_main.rs index a6a47117e75d..7059894b8f93 100644 --- a/codex-rs/linux-sandbox/src/linux_run_main.rs +++ b/codex-rs/linux-sandbox/src/linux_run_main.rs @@ -11,10 +11,9 @@ use crate::bwrap::BwrapNetworkMode; use crate::bwrap::BwrapOptions; use crate::bwrap::create_bwrap_command_args; use crate::landlock::apply_sandbox_policy_to_current_thread; +use crate::launcher::exec_bwrap; use crate::proxy_routing::activate_proxy_routes_in_netns; use crate::proxy_routing::prepare_host_proxy_route_spec; -use crate::vendored_bwrap::exec_vendored_bwrap; -use crate::vendored_bwrap::run_vendored_bwrap_main; use codex_protocol::protocol::FileSystemSandboxPolicy; use codex_protocol::protocol::NetworkSandboxPolicy; use codex_protocol::protocol::SandboxPolicy; @@ -434,7 +433,11 @@ fn run_bwrap_with_proc_fallback( command_cwd, options, ); - exec_vendored_bwrap(bwrap_args.args, bwrap_args.preserved_files); + exec_bwrap( + bwrap_args.args, + bwrap_args.preserved_files, + /*emit_missing_system_bwrap_warning*/ true, + ); } fn bwrap_network_mode( @@ -568,8 +571,11 @@ fn run_bwrap_in_child_capture_stderr(bwrap_args: crate::bwrap::BwrapArgs) -> Str close_fd_or_panic(write_fd, "close write end in bubblewrap child"); } - let exit_code = run_vendored_bwrap_main(&bwrap_args.args, &bwrap_args.preserved_files); - std::process::exit(exit_code); + exec_bwrap( + bwrap_args.args, + bwrap_args.preserved_files, + /*emit_missing_system_bwrap_warning*/ false, + ); } // Parent: close the write end and read stderr while the child runs. From bb7de0701438b5a292f8c88f33db7a28690a19ea Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Tue, 17 Mar 2026 11:00:33 -0700 Subject: [PATCH 02/14] fix(linux-sandbox): prefer system bwrap when available --- codex-rs/core/README.md | 8 ++-- codex-rs/linux-sandbox/README.md | 19 ++++----- codex-rs/linux-sandbox/src/launcher.rs | 58 +++++++------------------- 3 files changed, 26 insertions(+), 59 deletions(-) diff --git a/codex-rs/core/README.md b/codex-rs/core/README.md index 4d9ec91cb112..ba75401eed2e 100644 --- a/codex-rs/core/README.md +++ b/codex-rs/core/README.md @@ -60,10 +60,10 @@ only when the split filesystem policy round-trips through the legacy cases like `/repo = write`, `/repo/a = none`, `/repo/a/b = write`, where the more specific writable child must reopen under a denied parent. -On Ubuntu/AppArmor hosts that ship `/etc/apparmor.d/bwrap-userns-restrict`, -the Linux sandbox helper prefers `/usr/bin/bwrap` so the distro's userns -exception applies. Other Linux hosts continue to use the vendored bubblewrap -path compiled into the helper binary. +The Linux sandbox helper prefers `/usr/bin/bwrap` whenever it is available and +falls back to the vendored bubblewrap path otherwise. This also covers the +Ubuntu/AppArmor case where the distro's userns exception is path-specific to +`/usr/bin/bwrap`. ### All Platforms diff --git a/codex-rs/linux-sandbox/README.md b/codex-rs/linux-sandbox/README.md index 41e51db40b35..a00419d93ba4 100644 --- a/codex-rs/linux-sandbox/README.md +++ b/codex-rs/linux-sandbox/README.md @@ -7,21 +7,18 @@ This crate is responsible for producing: - the `codex-exec` CLI can check if its arg0 is `codex-linux-sandbox` and, if so, execute as if it were `codex-linux-sandbox` - this should also be true of the `codex` multitool CLI -On Linux, the bubblewrap pipeline prefers the system `/usr/bin/bwrap` when the -Ubuntu AppArmor `bwrap-userns-restrict` profile is installed, because that -profile grants user namespace setup specifically to the system path. Otherwise, -the helper uses the vendored bubblewrap path compiled into this binary. +On Linux, the bubblewrap pipeline prefers the system `/usr/bin/bwrap` whenever +it is available. If `/usr/bin/bwrap` is missing, the helper warns and uses the +vendored bubblewrap path compiled into this binary. **Current Behavior** - Legacy `SandboxPolicy` / `sandbox_mode` configs remain supported. - Bubblewrap is the default filesystem sandbox pipeline. -- On Ubuntu/AppArmor hosts that ship `/etc/apparmor.d/bwrap-userns-restrict`, - the helper prefers `/usr/bin/bwrap` so the distro AppArmor exception still - applies. -- If that AppArmor profile exists but `/usr/bin/bwrap` is missing, the helper - warns on stderr and falls back to the vendored bubblewrap path for now. -- On other Linux hosts, the helper continues to use the vendored bubblewrap - path compiled into the sandbox binary. +- If `/usr/bin/bwrap` is present, the helper uses it. +- If `/usr/bin/bwrap` is missing, the helper warns on stderr and falls back to + the vendored bubblewrap path. +- This also covers the Ubuntu/AppArmor case where the distro policy exception + is path-specific to `/usr/bin/bwrap`. - Legacy Landlock + mount protections remain available as an explicit legacy fallback path. - Set `features.use_legacy_landlock = true` (or CLI `-c use_legacy_landlock=true`) diff --git a/codex-rs/linux-sandbox/src/launcher.rs b/codex-rs/linux-sandbox/src/launcher.rs index 2c7095f1ee4f..e69e03ceb091 100644 --- a/codex-rs/linux-sandbox/src/launcher.rs +++ b/codex-rs/linux-sandbox/src/launcher.rs @@ -9,12 +9,11 @@ use std::path::PathBuf; use crate::vendored_bwrap::exec_vendored_bwrap; const SYSTEM_BWRAP_PATH: &str = "/usr/bin/bwrap"; -const UBUNTU_APPARMOR_BWRAP_USERNS_RESTRICT_PROFILE: &str = "/etc/apparmor.d/bwrap-userns-restrict"; #[derive(Debug, Clone, PartialEq, Eq)] enum BubblewrapLauncher { System(PathBuf), - Vendored { warn_missing_system_bwrap: bool }, + Vendored, } pub(crate) fn exec_bwrap( @@ -24,10 +23,8 @@ pub(crate) fn exec_bwrap( ) -> ! { match preferred_bwrap_launcher() { BubblewrapLauncher::System(program) => exec_system_bwrap(&program, argv, preserved_files), - BubblewrapLauncher::Vendored { - warn_missing_system_bwrap, - } => { - if warn_missing_system_bwrap && emit_missing_system_bwrap_warning { + BubblewrapLauncher::Vendored => { + if emit_missing_system_bwrap_warning { eprintln!( "warning: Codex could not find system bubblewrap at {SYSTEM_BWRAP_PATH}. Please install bubblewrap with your package manager. Codex will use the vendored bubblewrap in the meantime." ); @@ -38,24 +35,16 @@ pub(crate) fn exec_bwrap( } fn preferred_bwrap_launcher() -> BubblewrapLauncher { - let apparmor_profile_exists = Path::new(UBUNTU_APPARMOR_BWRAP_USERNS_RESTRICT_PROFILE).exists(); let system_bwrap_exists = Path::new(SYSTEM_BWRAP_PATH).exists(); - preferred_bwrap_launcher_with(apparmor_profile_exists, system_bwrap_exists) + preferred_bwrap_launcher_with(system_bwrap_exists) } -fn preferred_bwrap_launcher_with( - apparmor_profile_exists: bool, - system_bwrap_exists: bool, -) -> BubblewrapLauncher { - match (apparmor_profile_exists, system_bwrap_exists) { - (true, true) => BubblewrapLauncher::System(PathBuf::from(SYSTEM_BWRAP_PATH)), - (true, false) => BubblewrapLauncher::Vendored { - warn_missing_system_bwrap: true, - }, - (false, _) => BubblewrapLauncher::Vendored { - warn_missing_system_bwrap: false, - }, +fn preferred_bwrap_launcher_with(system_bwrap_exists: bool) -> BubblewrapLauncher { + if system_bwrap_exists { + BubblewrapLauncher::System(PathBuf::from(SYSTEM_BWRAP_PATH)) + } else { + BubblewrapLauncher::Vendored } } @@ -122,37 +111,18 @@ mod tests { use tempfile::NamedTempFile; #[test] - fn prefers_system_bwrap_for_ubuntu_apparmor_profile() { + fn prefers_system_bwrap_when_present() { assert_eq!( - preferred_bwrap_launcher_with( - /*apparmor_profile_exists*/ true, /*system_bwrap_exists*/ true - ), + preferred_bwrap_launcher_with(/*system_bwrap_exists*/ true), BubblewrapLauncher::System(PathBuf::from(SYSTEM_BWRAP_PATH)) ); } #[test] - fn falls_back_to_vendored_without_warning_when_apparmor_profile_is_absent() { + fn falls_back_to_vendored_when_system_bwrap_is_missing() { assert_eq!( - preferred_bwrap_launcher_with( - /*apparmor_profile_exists*/ false, /*system_bwrap_exists*/ true - ), - BubblewrapLauncher::Vendored { - warn_missing_system_bwrap: false, - } - ); - } - - #[test] - fn falls_back_to_vendored_with_warning_when_apparmor_profile_exists_but_system_bwrap_is_missing() - { - assert_eq!( - preferred_bwrap_launcher_with( - /*apparmor_profile_exists*/ true, /*system_bwrap_exists*/ false - ), - BubblewrapLauncher::Vendored { - warn_missing_system_bwrap: true, - } + preferred_bwrap_launcher_with(/*system_bwrap_exists*/ false), + BubblewrapLauncher::Vendored ); } From acad3478eb79bfb0537d4129830c7db06580627a Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Tue, 17 Mar 2026 11:01:57 -0700 Subject: [PATCH 03/14] fix(linux-sandbox): remove stale vendored bwrap export --- codex-rs/linux-sandbox/src/vendored_bwrap.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/codex-rs/linux-sandbox/src/vendored_bwrap.rs b/codex-rs/linux-sandbox/src/vendored_bwrap.rs index 538552268718..a2da14db0571 100644 --- a/codex-rs/linux-sandbox/src/vendored_bwrap.rs +++ b/codex-rs/linux-sandbox/src/vendored_bwrap.rs @@ -76,4 +76,3 @@ Notes: } pub(crate) use imp::exec_vendored_bwrap; -pub(crate) use imp::run_vendored_bwrap_main; From 1a44c087427867eef29bcd62aac763fcf9c323a0 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Tue, 17 Mar 2026 11:05:14 -0700 Subject: [PATCH 04/14] refactor(linux-sandbox): inline bwrap launcher selector --- codex-rs/linux-sandbox/src/launcher.rs | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/codex-rs/linux-sandbox/src/launcher.rs b/codex-rs/linux-sandbox/src/launcher.rs index e69e03ceb091..42a1233d75c7 100644 --- a/codex-rs/linux-sandbox/src/launcher.rs +++ b/codex-rs/linux-sandbox/src/launcher.rs @@ -35,13 +35,7 @@ pub(crate) fn exec_bwrap( } fn preferred_bwrap_launcher() -> BubblewrapLauncher { - let system_bwrap_exists = Path::new(SYSTEM_BWRAP_PATH).exists(); - - preferred_bwrap_launcher_with(system_bwrap_exists) -} - -fn preferred_bwrap_launcher_with(system_bwrap_exists: bool) -> BubblewrapLauncher { - if system_bwrap_exists { + if Path::new(SYSTEM_BWRAP_PATH).exists() { BubblewrapLauncher::System(PathBuf::from(SYSTEM_BWRAP_PATH)) } else { BubblewrapLauncher::Vendored @@ -110,22 +104,6 @@ mod tests { use pretty_assertions::assert_eq; use tempfile::NamedTempFile; - #[test] - fn prefers_system_bwrap_when_present() { - assert_eq!( - preferred_bwrap_launcher_with(/*system_bwrap_exists*/ true), - BubblewrapLauncher::System(PathBuf::from(SYSTEM_BWRAP_PATH)) - ); - } - - #[test] - fn falls_back_to_vendored_when_system_bwrap_is_missing() { - assert_eq!( - preferred_bwrap_launcher_with(/*system_bwrap_exists*/ false), - BubblewrapLauncher::Vendored - ); - } - #[test] fn preserved_files_are_made_inheritable_for_system_exec() { let file = NamedTempFile::new().expect("temp file"); From 42005afa20edbcb2f41e50836f080b89a4e7f1b3 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Tue, 17 Mar 2026 11:16:31 -0700 Subject: [PATCH 05/14] fix(linux-sandbox): tighten system bwrap launcher --- codex-rs/linux-sandbox/src/launcher.rs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/codex-rs/linux-sandbox/src/launcher.rs b/codex-rs/linux-sandbox/src/launcher.rs index 42a1233d75c7..41ec0c0ff789 100644 --- a/codex-rs/linux-sandbox/src/launcher.rs +++ b/codex-rs/linux-sandbox/src/launcher.rs @@ -1,18 +1,19 @@ use std::ffi::CString; use std::fs::File; +use std::io::IsTerminal; use std::os::fd::AsRawFd; use std::os::raw::c_char; use std::os::unix::ffi::OsStrExt; use std::path::Path; -use std::path::PathBuf; use crate::vendored_bwrap::exec_vendored_bwrap; +use codex_utils_absolute_path::AbsolutePathBuf; const SYSTEM_BWRAP_PATH: &str = "/usr/bin/bwrap"; #[derive(Debug, Clone, PartialEq, Eq)] enum BubblewrapLauncher { - System(PathBuf), + System(AbsolutePathBuf), Vendored, } @@ -24,7 +25,7 @@ pub(crate) fn exec_bwrap( match preferred_bwrap_launcher() { BubblewrapLauncher::System(program) => exec_system_bwrap(&program, argv, preserved_files), BubblewrapLauncher::Vendored => { - if emit_missing_system_bwrap_warning { + if emit_missing_system_bwrap_warning && std::io::stderr().is_terminal() { eprintln!( "warning: Codex could not find system bubblewrap at {SYSTEM_BWRAP_PATH}. Please install bubblewrap with your package manager. Codex will use the vendored bubblewrap in the meantime." ); @@ -35,18 +36,25 @@ pub(crate) fn exec_bwrap( } fn preferred_bwrap_launcher() -> BubblewrapLauncher { - if Path::new(SYSTEM_BWRAP_PATH).exists() { - BubblewrapLauncher::System(PathBuf::from(SYSTEM_BWRAP_PATH)) + let system_bwrap_path = AbsolutePathBuf::from_absolute_path(Path::new(SYSTEM_BWRAP_PATH)) + .expect("system bubblewrap path should be absolute"); + if system_bwrap_path.as_path().is_file() { + BubblewrapLauncher::System(system_bwrap_path) } else { BubblewrapLauncher::Vendored } } -fn exec_system_bwrap(program: &Path, argv: Vec, preserved_files: Vec) -> ! { +fn exec_system_bwrap( + program: &AbsolutePathBuf, + argv: Vec, + preserved_files: Vec, +) -> ! { + // System bwrap runs across an exec boundary, so preserved fds must survive exec. make_files_inheritable(&preserved_files); - let program_path = program.display().to_string(); - let program = CString::new(program.as_os_str().as_bytes()) + let program_path = program.as_path().display().to_string(); + let program = CString::new(program.as_path().as_os_str().as_bytes()) .unwrap_or_else(|err| panic!("invalid system bubblewrap path: {err}")); let cstrings = argv_to_cstrings(&argv); let mut argv_ptrs: Vec<*const c_char> = cstrings.iter().map(|arg| arg.as_ptr()).collect(); From 914e69fa949d1023fd846bfe58fee57324f6541b Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Tue, 17 Mar 2026 11:45:32 -0700 Subject: [PATCH 06/14] fix(linux-sandbox): route missing bwrap warning upstream --- codex-rs/app-server/src/lib.rs | 30 +++++++++++++++++++- codex-rs/core/README.md | 7 +++-- codex-rs/linux-sandbox/README.md | 17 +++++++---- codex-rs/linux-sandbox/src/launcher.rs | 16 ++--------- codex-rs/linux-sandbox/src/linux_run_main.rs | 12 ++------ codex-rs/tui_app_server/src/lib.rs | 29 ++++++++++++++++++- 6 files changed, 76 insertions(+), 35 deletions(-) diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index 4bec5b3c9dd3..0448d61afdb6 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -13,6 +13,8 @@ use std::collections::HashMap; use std::collections::HashSet; use std::io::ErrorKind; use std::io::Result as IoResult; +#[cfg(target_os = "linux")] +use std::path::Path; use std::sync::Arc; use std::sync::RwLock; use std::sync::atomic::AtomicBool; @@ -81,6 +83,10 @@ pub use crate::error_code::INVALID_PARAMS_ERROR_CODE; pub use crate::transport::AppServerTransport; const LOG_FORMAT_ENV_VAR: &str = "LOG_FORMAT"; +#[cfg(target_os = "linux")] +const UBUNTU_APPARMOR_BWRAP_USERNS_RESTRICT_PROFILE: &str = "/etc/apparmor.d/bwrap-userns-restrict"; +#[cfg(target_os = "linux")] +const SYSTEM_BWRAP_PATH: &str = "/usr/bin/bwrap"; #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum LogFormat { @@ -310,6 +316,24 @@ fn project_config_warning(config: &Config) -> Option }) } +#[cfg(target_os = "linux")] +fn missing_system_bwrap_warning() -> Option { + if Path::new(UBUNTU_APPARMOR_BWRAP_USERNS_RESTRICT_PROFILE).is_file() + && !Path::new(SYSTEM_BWRAP_PATH).is_file() + { + return Some(format!( + "Codex could not find system bubblewrap at {SYSTEM_BWRAP_PATH}. Please install bubblewrap with your package manager. Codex will use the vendored bubblewrap in the meantime." + )); + } + + None +} + +#[cfg(not(target_os = "linux"))] +fn missing_system_bwrap_warning() -> Option { + None +} + impl LogFormat { fn from_env_value(value: Option<&str>) -> Self { match value.map(str::trim).map(str::to_ascii_lowercase) { @@ -435,7 +459,7 @@ pub async fn run_main_with_transport( }; let loader_overrides_for_config_api = loader_overrides.clone(); let mut config_warnings = Vec::new(); - let config = match ConfigBuilder::default() + let mut config = match ConfigBuilder::default() .cli_overrides(cli_kv_overrides.clone()) .loader_overrides(loader_overrides) .cloud_requirements(cloud_requirements.clone()) @@ -455,6 +479,10 @@ pub async fn run_main_with_transport( } }; + if let Some(warning) = missing_system_bwrap_warning() { + config.startup_warnings.push(warning); + } + if let Ok(Some(err)) = check_execpolicy_for_warnings(&config.config_layer_stack).await { let (path, range) = exec_policy_warning_location(&err); let message = ConfigWarningNotification { diff --git a/codex-rs/core/README.md b/codex-rs/core/README.md index ba75401eed2e..d0ce8fa88859 100644 --- a/codex-rs/core/README.md +++ b/codex-rs/core/README.md @@ -61,9 +61,10 @@ cases like `/repo = write`, `/repo/a = none`, `/repo/a/b = write`, where the more specific writable child must reopen under a denied parent. The Linux sandbox helper prefers `/usr/bin/bwrap` whenever it is available and -falls back to the vendored bubblewrap path otherwise. This also covers the -Ubuntu/AppArmor case where the distro's userns exception is path-specific to -`/usr/bin/bwrap`. +falls back to the vendored bubblewrap path otherwise. On Ubuntu/AppArmor hosts +that ship `/etc/apparmor.d/bwrap-userns-restrict`, Codex also surfaces a +startup warning when `/usr/bin/bwrap` is missing because the distro's userns +exception is path-specific to `/usr/bin/bwrap`. ### All Platforms diff --git a/codex-rs/linux-sandbox/README.md b/codex-rs/linux-sandbox/README.md index a00419d93ba4..cc20729d416f 100644 --- a/codex-rs/linux-sandbox/README.md +++ b/codex-rs/linux-sandbox/README.md @@ -8,17 +8,22 @@ This crate is responsible for producing: - this should also be true of the `codex` multitool CLI On Linux, the bubblewrap pipeline prefers the system `/usr/bin/bwrap` whenever -it is available. If `/usr/bin/bwrap` is missing, the helper warns and uses the -vendored bubblewrap path compiled into this binary. +it is available. If `/usr/bin/bwrap` is missing, the helper still falls back to +the vendored bubblewrap path compiled into this binary. + +On Ubuntu/AppArmor hosts that ship `/etc/apparmor.d/bwrap-userns-restrict`, +Codex also surfaces a startup warning when `/usr/bin/bwrap` is missing because +the distro's user-namespace exception is path-specific to `/usr/bin/bwrap`. **Current Behavior** - Legacy `SandboxPolicy` / `sandbox_mode` configs remain supported. - Bubblewrap is the default filesystem sandbox pipeline. - If `/usr/bin/bwrap` is present, the helper uses it. -- If `/usr/bin/bwrap` is missing, the helper warns on stderr and falls back to - the vendored bubblewrap path. -- This also covers the Ubuntu/AppArmor case where the distro policy exception - is path-specific to `/usr/bin/bwrap`. +- If `/usr/bin/bwrap` is missing, the helper falls back to the vendored + bubblewrap path. +- On Ubuntu/AppArmor hosts with `bwrap-userns-restrict`, Codex surfaces a + startup warning when `/usr/bin/bwrap` is missing because the distro policy + exception is path-specific to `/usr/bin/bwrap`. - Legacy Landlock + mount protections remain available as an explicit legacy fallback path. - Set `features.use_legacy_landlock = true` (or CLI `-c use_legacy_landlock=true`) diff --git a/codex-rs/linux-sandbox/src/launcher.rs b/codex-rs/linux-sandbox/src/launcher.rs index 41ec0c0ff789..70ce8e15bfa8 100644 --- a/codex-rs/linux-sandbox/src/launcher.rs +++ b/codex-rs/linux-sandbox/src/launcher.rs @@ -1,6 +1,5 @@ use std::ffi::CString; use std::fs::File; -use std::io::IsTerminal; use std::os::fd::AsRawFd; use std::os::raw::c_char; use std::os::unix::ffi::OsStrExt; @@ -17,21 +16,10 @@ enum BubblewrapLauncher { Vendored, } -pub(crate) fn exec_bwrap( - argv: Vec, - preserved_files: Vec, - emit_missing_system_bwrap_warning: bool, -) -> ! { +pub(crate) fn exec_bwrap(argv: Vec, preserved_files: Vec) -> ! { match preferred_bwrap_launcher() { BubblewrapLauncher::System(program) => exec_system_bwrap(&program, argv, preserved_files), - BubblewrapLauncher::Vendored => { - if emit_missing_system_bwrap_warning && std::io::stderr().is_terminal() { - eprintln!( - "warning: Codex could not find system bubblewrap at {SYSTEM_BWRAP_PATH}. Please install bubblewrap with your package manager. Codex will use the vendored bubblewrap in the meantime." - ); - } - exec_vendored_bwrap(argv, preserved_files) - } + BubblewrapLauncher::Vendored => exec_vendored_bwrap(argv, preserved_files), } } diff --git a/codex-rs/linux-sandbox/src/linux_run_main.rs b/codex-rs/linux-sandbox/src/linux_run_main.rs index 7059894b8f93..b753460dcba7 100644 --- a/codex-rs/linux-sandbox/src/linux_run_main.rs +++ b/codex-rs/linux-sandbox/src/linux_run_main.rs @@ -433,11 +433,7 @@ fn run_bwrap_with_proc_fallback( command_cwd, options, ); - exec_bwrap( - bwrap_args.args, - bwrap_args.preserved_files, - /*emit_missing_system_bwrap_warning*/ true, - ); + exec_bwrap(bwrap_args.args, bwrap_args.preserved_files); } fn bwrap_network_mode( @@ -571,11 +567,7 @@ fn run_bwrap_in_child_capture_stderr(bwrap_args: crate::bwrap::BwrapArgs) -> Str close_fd_or_panic(write_fd, "close write end in bubblewrap child"); } - exec_bwrap( - bwrap_args.args, - bwrap_args.preserved_files, - /*emit_missing_system_bwrap_warning*/ false, - ); + exec_bwrap(bwrap_args.args, bwrap_args.preserved_files); } // Parent: close the write end and read stderr while the child runs. diff --git a/codex-rs/tui_app_server/src/lib.rs b/codex-rs/tui_app_server/src/lib.rs index 0546d88487a3..869f4a9f6c27 100644 --- a/codex-rs/tui_app_server/src/lib.rs +++ b/codex-rs/tui_app_server/src/lib.rs @@ -118,6 +118,29 @@ mod selection_list; mod session_log; mod shimmer; mod skills_helpers; + +#[cfg(target_os = "linux")] +const UBUNTU_APPARMOR_BWRAP_USERNS_RESTRICT_PROFILE: &str = "/etc/apparmor.d/bwrap-userns-restrict"; +#[cfg(target_os = "linux")] +const SYSTEM_BWRAP_PATH: &str = "/usr/bin/bwrap"; + +#[cfg(target_os = "linux")] +fn missing_system_bwrap_warning() -> Option { + if Path::new(UBUNTU_APPARMOR_BWRAP_USERNS_RESTRICT_PROFILE).is_file() + && !Path::new(SYSTEM_BWRAP_PATH).is_file() + { + return Some(format!( + "Codex could not find system bubblewrap at {SYSTEM_BWRAP_PATH}. Please install bubblewrap with your package manager. Codex will use the vendored bubblewrap in the meantime." + )); + } + + None +} + +#[cfg(not(target_os = "linux"))] +fn missing_system_bwrap_warning() -> Option { + None +} mod slash_command; mod status; mod status_indicator_widget; @@ -387,7 +410,7 @@ pub(crate) async fn start_embedded_app_server_for_picker( async fn start_embedded_app_server_with( arg0_paths: Arg0DispatchPaths, - config: Config, + mut config: Config, cli_kv_overrides: Vec<(String, toml::Value)>, loader_overrides: LoaderOverrides, cloud_requirements: CloudRequirementsLoader, @@ -398,6 +421,10 @@ where F: FnOnce(InProcessClientStartArgs) -> Fut, Fut: Future>, { + if let Some(warning) = missing_system_bwrap_warning() { + config.startup_warnings.push(warning); + } + let config_warnings = config .startup_warnings .iter() From b4ae458ff02829c3d0ed53bed87b6e879551c82e Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Tue, 17 Mar 2026 12:36:56 -0700 Subject: [PATCH 07/14] fix(linux-sandbox): simplify missing bwrap warning --- codex-rs/app-server/src/lib.rs | 6 +----- codex-rs/core/README.md | 7 +++---- codex-rs/linux-sandbox/README.md | 11 ++++------- codex-rs/tui_app_server/src/lib.rs | 6 +----- 4 files changed, 9 insertions(+), 21 deletions(-) diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index 0448d61afdb6..eb16aec0de19 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -84,8 +84,6 @@ pub use crate::transport::AppServerTransport; const LOG_FORMAT_ENV_VAR: &str = "LOG_FORMAT"; #[cfg(target_os = "linux")] -const UBUNTU_APPARMOR_BWRAP_USERNS_RESTRICT_PROFILE: &str = "/etc/apparmor.d/bwrap-userns-restrict"; -#[cfg(target_os = "linux")] const SYSTEM_BWRAP_PATH: &str = "/usr/bin/bwrap"; #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -318,9 +316,7 @@ fn project_config_warning(config: &Config) -> Option #[cfg(target_os = "linux")] fn missing_system_bwrap_warning() -> Option { - if Path::new(UBUNTU_APPARMOR_BWRAP_USERNS_RESTRICT_PROFILE).is_file() - && !Path::new(SYSTEM_BWRAP_PATH).is_file() - { + if !Path::new(SYSTEM_BWRAP_PATH).is_file() { return Some(format!( "Codex could not find system bubblewrap at {SYSTEM_BWRAP_PATH}. Please install bubblewrap with your package manager. Codex will use the vendored bubblewrap in the meantime." )); diff --git a/codex-rs/core/README.md b/codex-rs/core/README.md index d0ce8fa88859..77966ea88c62 100644 --- a/codex-rs/core/README.md +++ b/codex-rs/core/README.md @@ -61,10 +61,9 @@ cases like `/repo = write`, `/repo/a = none`, `/repo/a/b = write`, where the more specific writable child must reopen under a denied parent. The Linux sandbox helper prefers `/usr/bin/bwrap` whenever it is available and -falls back to the vendored bubblewrap path otherwise. On Ubuntu/AppArmor hosts -that ship `/etc/apparmor.d/bwrap-userns-restrict`, Codex also surfaces a -startup warning when `/usr/bin/bwrap` is missing because the distro's userns -exception is path-specific to `/usr/bin/bwrap`. +falls back to the vendored bubblewrap path otherwise. When `/usr/bin/bwrap` is +missing, Codex also surfaces a startup warning through its normal notification +path instead of printing directly from the sandbox helper. ### All Platforms diff --git a/codex-rs/linux-sandbox/README.md b/codex-rs/linux-sandbox/README.md index cc20729d416f..3fde7d9738ae 100644 --- a/codex-rs/linux-sandbox/README.md +++ b/codex-rs/linux-sandbox/README.md @@ -10,10 +10,8 @@ This crate is responsible for producing: On Linux, the bubblewrap pipeline prefers the system `/usr/bin/bwrap` whenever it is available. If `/usr/bin/bwrap` is missing, the helper still falls back to the vendored bubblewrap path compiled into this binary. - -On Ubuntu/AppArmor hosts that ship `/etc/apparmor.d/bwrap-userns-restrict`, -Codex also surfaces a startup warning when `/usr/bin/bwrap` is missing because -the distro's user-namespace exception is path-specific to `/usr/bin/bwrap`. +Codex also surfaces a startup warning when `/usr/bin/bwrap` is missing so users +know it is falling back to the vendored helper. **Current Behavior** - Legacy `SandboxPolicy` / `sandbox_mode` configs remain supported. @@ -21,9 +19,8 @@ the distro's user-namespace exception is path-specific to `/usr/bin/bwrap`. - If `/usr/bin/bwrap` is present, the helper uses it. - If `/usr/bin/bwrap` is missing, the helper falls back to the vendored bubblewrap path. -- On Ubuntu/AppArmor hosts with `bwrap-userns-restrict`, Codex surfaces a - startup warning when `/usr/bin/bwrap` is missing because the distro policy - exception is path-specific to `/usr/bin/bwrap`. +- If `/usr/bin/bwrap` is missing, Codex also surfaces a startup warning instead + of printing directly from the sandbox helper. - Legacy Landlock + mount protections remain available as an explicit legacy fallback path. - Set `features.use_legacy_landlock = true` (or CLI `-c use_legacy_landlock=true`) diff --git a/codex-rs/tui_app_server/src/lib.rs b/codex-rs/tui_app_server/src/lib.rs index 869f4a9f6c27..8dd291202ca6 100644 --- a/codex-rs/tui_app_server/src/lib.rs +++ b/codex-rs/tui_app_server/src/lib.rs @@ -119,16 +119,12 @@ mod session_log; mod shimmer; mod skills_helpers; -#[cfg(target_os = "linux")] -const UBUNTU_APPARMOR_BWRAP_USERNS_RESTRICT_PROFILE: &str = "/etc/apparmor.d/bwrap-userns-restrict"; #[cfg(target_os = "linux")] const SYSTEM_BWRAP_PATH: &str = "/usr/bin/bwrap"; #[cfg(target_os = "linux")] fn missing_system_bwrap_warning() -> Option { - if Path::new(UBUNTU_APPARMOR_BWRAP_USERNS_RESTRICT_PROFILE).is_file() - && !Path::new(SYSTEM_BWRAP_PATH).is_file() - { + if !Path::new(SYSTEM_BWRAP_PATH).is_file() { return Some(format!( "Codex could not find system bubblewrap at {SYSTEM_BWRAP_PATH}. Please install bubblewrap with your package manager. Codex will use the vendored bubblewrap in the meantime." )); From 6cbdf455d3a77e544a9c6fd899ad160dd97a884f Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Tue, 17 Mar 2026 12:47:28 -0700 Subject: [PATCH 08/14] fix(linux-sandbox): share missing bwrap warning --- codex-rs/app-server/src/lib.rs | 25 ++----------------------- codex-rs/core/src/config/mod.rs | 21 +++++++++++++++++++++ codex-rs/exec/src/lib.rs | 4 +++- codex-rs/tui/src/lib.rs | 6 +++++- codex-rs/tui_app_server/src/lib.rs | 29 ++++++----------------------- 5 files changed, 37 insertions(+), 48 deletions(-) diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index eb16aec0de19..e62d0b9ccc5b 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -5,6 +5,7 @@ use codex_cloud_requirements::cloud_requirements_loader; use codex_core::AuthManager; use codex_core::config::Config; use codex_core::config::ConfigBuilder; +use codex_core::config::push_missing_system_bwrap_startup_warning; use codex_core::config_loader::CloudRequirementsLoader; use codex_core::config_loader::ConfigLayerStackOrdering; use codex_core::config_loader::LoaderOverrides; @@ -13,8 +14,6 @@ use std::collections::HashMap; use std::collections::HashSet; use std::io::ErrorKind; use std::io::Result as IoResult; -#[cfg(target_os = "linux")] -use std::path::Path; use std::sync::Arc; use std::sync::RwLock; use std::sync::atomic::AtomicBool; @@ -83,8 +82,6 @@ pub use crate::error_code::INVALID_PARAMS_ERROR_CODE; pub use crate::transport::AppServerTransport; const LOG_FORMAT_ENV_VAR: &str = "LOG_FORMAT"; -#[cfg(target_os = "linux")] -const SYSTEM_BWRAP_PATH: &str = "/usr/bin/bwrap"; #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum LogFormat { @@ -314,22 +311,6 @@ fn project_config_warning(config: &Config) -> Option }) } -#[cfg(target_os = "linux")] -fn missing_system_bwrap_warning() -> Option { - if !Path::new(SYSTEM_BWRAP_PATH).is_file() { - return Some(format!( - "Codex could not find system bubblewrap at {SYSTEM_BWRAP_PATH}. Please install bubblewrap with your package manager. Codex will use the vendored bubblewrap in the meantime." - )); - } - - None -} - -#[cfg(not(target_os = "linux"))] -fn missing_system_bwrap_warning() -> Option { - None -} - impl LogFormat { fn from_env_value(value: Option<&str>) -> Self { match value.map(str::trim).map(str::to_ascii_lowercase) { @@ -475,9 +456,7 @@ pub async fn run_main_with_transport( } }; - if let Some(warning) = missing_system_bwrap_warning() { - config.startup_warnings.push(warning); - } + push_missing_system_bwrap_startup_warning(&mut config.startup_warnings); if let Ok(Some(err)) = check_execpolicy_for_warnings(&config.config_layer_stack).await { let (path, range) = exec_policy_warning_location(&err); diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 7a543161e6ca..4019e17b4649 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -140,12 +140,33 @@ pub(crate) const DEFAULT_AGENT_JOB_MAX_RUNTIME_SECONDS: Option = None; pub const CONFIG_TOML_FILE: &str = "config.toml"; const OPENAI_BASE_URL_ENV_VAR: &str = "OPENAI_BASE_URL"; +#[cfg(target_os = "linux")] +const SYSTEM_BWRAP_PATH: &str = "/usr/bin/bwrap"; const RESERVED_MODEL_PROVIDER_IDS: [&str; 3] = [ OPENAI_PROVIDER_ID, OLLAMA_OSS_PROVIDER_ID, LMSTUDIO_OSS_PROVIDER_ID, ]; +/// Add a shared startup warning when Linux will fall back to vendored +/// bubblewrap because `/usr/bin/bwrap` is unavailable. +#[cfg(target_os = "linux")] +pub fn push_missing_system_bwrap_startup_warning(startup_warnings: &mut Vec) { + if Path::new(SYSTEM_BWRAP_PATH).is_file() { + return; + } + + let message = format!( + "Codex could not find system bubblewrap at {SYSTEM_BWRAP_PATH}. Please install bubblewrap with your package manager. Codex will use the vendored bubblewrap in the meantime." + ); + if !startup_warnings.contains(&message) { + startup_warnings.push(message); + } +} + +#[cfg(not(target_os = "linux"))] +pub fn push_missing_system_bwrap_startup_warning(_startup_warnings: &mut Vec) {} + fn resolve_sqlite_home_env(resolved_cwd: &Path) -> Option { let raw = std::env::var(codex_state::SQLITE_HOME_ENV).ok()?; let trimmed = raw.trim(); diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index fdaca4b1f977..96bbfb887de7 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -52,6 +52,7 @@ use codex_core::config::ConfigBuilder; use codex_core::config::ConfigOverrides; use codex_core::config::find_codex_home; use codex_core::config::load_config_as_toml_with_cli_overrides; +use codex_core::config::push_missing_system_bwrap_startup_warning; use codex_core::config::resolve_oss_provider; use codex_core::config_loader::ConfigLoadError; use codex_core::config_loader::LoaderOverrides; @@ -360,12 +361,13 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result additional_writable_roots: add_dir, }; - let config = ConfigBuilder::default() + let mut config = ConfigBuilder::default() .cli_overrides(cli_kv_overrides) .harness_overrides(overrides) .cloud_requirements(cloud_requirements) .build() .await?; + push_missing_system_bwrap_startup_warning(&mut config.startup_warnings); #[allow(clippy::print_stderr)] match check_execpolicy_for_warnings(&config.config_layer_stack).await { diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index f537c95bbc03..186026b20a0e 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -21,6 +21,7 @@ use codex_core::config::ConfigBuilder; use codex_core::config::ConfigOverrides; use codex_core::config::find_codex_home; use codex_core::config::load_config_as_toml_with_cli_overrides; +use codex_core::config::push_missing_system_bwrap_startup_warning; use codex_core::config::resolve_oss_provider; use codex_core::config_loader::CloudRequirementsLoader; use codex_core::config_loader::ConfigLoadError; @@ -1176,7 +1177,10 @@ async fn load_config_or_exit_with_fallback_cwd( .build() .await { - Ok(config) => config, + Ok(mut config) => { + push_missing_system_bwrap_startup_warning(&mut config.startup_warnings); + config + } Err(err) => { eprintln!("Error loading configuration: {err}"); std::process::exit(1); diff --git a/codex-rs/tui_app_server/src/lib.rs b/codex-rs/tui_app_server/src/lib.rs index 8dd291202ca6..78c22a7e0be5 100644 --- a/codex-rs/tui_app_server/src/lib.rs +++ b/codex-rs/tui_app_server/src/lib.rs @@ -28,6 +28,7 @@ use codex_core::config::ConfigBuilder; use codex_core::config::ConfigOverrides; use codex_core::config::find_codex_home; use codex_core::config::load_config_as_toml_with_cli_overrides; +use codex_core::config::push_missing_system_bwrap_startup_warning; use codex_core::config::resolve_oss_provider; use codex_core::config_loader::CloudRequirementsLoader; use codex_core::config_loader::ConfigLoadError; @@ -118,25 +119,6 @@ mod selection_list; mod session_log; mod shimmer; mod skills_helpers; - -#[cfg(target_os = "linux")] -const SYSTEM_BWRAP_PATH: &str = "/usr/bin/bwrap"; - -#[cfg(target_os = "linux")] -fn missing_system_bwrap_warning() -> Option { - if !Path::new(SYSTEM_BWRAP_PATH).is_file() { - return Some(format!( - "Codex could not find system bubblewrap at {SYSTEM_BWRAP_PATH}. Please install bubblewrap with your package manager. Codex will use the vendored bubblewrap in the meantime." - )); - } - - None -} - -#[cfg(not(target_os = "linux"))] -fn missing_system_bwrap_warning() -> Option { - None -} mod slash_command; mod status; mod status_indicator_widget; @@ -417,9 +399,7 @@ where F: FnOnce(InProcessClientStartArgs) -> Fut, Fut: Future>, { - if let Some(warning) = missing_system_bwrap_warning() { - config.startup_warnings.push(warning); - } + push_missing_system_bwrap_startup_warning(&mut config.startup_warnings); let config_warnings = config .startup_warnings @@ -1534,7 +1514,10 @@ async fn load_config_or_exit_with_fallback_cwd( .build() .await { - Ok(config) => config, + Ok(mut config) => { + push_missing_system_bwrap_startup_warning(&mut config.startup_warnings); + config + } Err(err) => { eprintln!("Error loading configuration: {err}"); std::process::exit(1); From 7d0f0cb7f19569e0b17527417177dbe64c2c86c2 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Tue, 17 Mar 2026 13:12:35 -0700 Subject: [PATCH 09/14] fix(linux-sandbox): finalize shared bwrap warning --- codex-rs/app-server/src/lib.rs | 5 +--- codex-rs/core/src/config/config_tests.rs | 29 ++++++++++++++++++++++++ codex-rs/core/src/config/mod.rs | 7 +++--- codex-rs/exec/src/lib.rs | 4 +--- codex-rs/tui/src/lib.rs | 6 +---- codex-rs/tui_app_server/src/lib.rs | 8 +------ 6 files changed, 36 insertions(+), 23 deletions(-) diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index e62d0b9ccc5b..4bec5b3c9dd3 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -5,7 +5,6 @@ use codex_cloud_requirements::cloud_requirements_loader; use codex_core::AuthManager; use codex_core::config::Config; use codex_core::config::ConfigBuilder; -use codex_core::config::push_missing_system_bwrap_startup_warning; use codex_core::config_loader::CloudRequirementsLoader; use codex_core::config_loader::ConfigLayerStackOrdering; use codex_core::config_loader::LoaderOverrides; @@ -436,7 +435,7 @@ pub async fn run_main_with_transport( }; let loader_overrides_for_config_api = loader_overrides.clone(); let mut config_warnings = Vec::new(); - let mut config = match ConfigBuilder::default() + let config = match ConfigBuilder::default() .cli_overrides(cli_kv_overrides.clone()) .loader_overrides(loader_overrides) .cloud_requirements(cloud_requirements.clone()) @@ -456,8 +455,6 @@ pub async fn run_main_with_transport( } }; - push_missing_system_bwrap_startup_warning(&mut config.startup_warnings); - if let Ok(Some(err)) = check_execpolicy_for_warnings(&config.config_layer_stack).await { let (path, range) = exec_policy_warning_location(&err); let message = ConfigWarningNotification { diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index 7e82bd7da9dc..cd0f105c5a1a 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -30,6 +30,7 @@ use pretty_assertions::assert_eq; use std::collections::BTreeMap; use std::collections::HashMap; +use std::path::Path; use std::time::Duration; use tempfile::TempDir; @@ -5496,6 +5497,34 @@ shell_tool = true Ok(()) } +#[tokio::test] +async fn missing_system_bwrap_warning_is_added_during_config_load() -> std::io::Result<()> { + let codex_home = TempDir::new()?; + + let config = ConfigBuilder::default() + .codex_home(codex_home.path().to_path_buf()) + .fallback_cwd(Some(codex_home.path().to_path_buf())) + .build() + .await?; + + let has_warning = config.startup_warnings.iter().any(|warning| { + warning.contains("Codex could not find system bubblewrap at /usr/bin/bwrap") + }); + + #[cfg(target_os = "linux")] + assert_eq!( + has_warning, + !Path::new("/usr/bin/bwrap").is_file(), + "{:?}", + config.startup_warnings + ); + + #[cfg(not(target_os = "linux"))] + assert!(!has_warning, "{:?}", config.startup_warnings); + + Ok(()) +} + #[tokio::test] async fn approvals_reviewer_defaults_to_manual_only_without_guardian_feature() -> std::io::Result<()> { diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 4019e17b4649..6f437de20f65 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -148,10 +148,8 @@ const RESERVED_MODEL_PROVIDER_IDS: [&str; 3] = [ LMSTUDIO_OSS_PROVIDER_ID, ]; -/// Add a shared startup warning when Linux will fall back to vendored -/// bubblewrap because `/usr/bin/bwrap` is unavailable. #[cfg(target_os = "linux")] -pub fn push_missing_system_bwrap_startup_warning(startup_warnings: &mut Vec) { +fn push_missing_system_bwrap_startup_warning(startup_warnings: &mut Vec) { if Path::new(SYSTEM_BWRAP_PATH).is_file() { return; } @@ -165,7 +163,7 @@ pub fn push_missing_system_bwrap_startup_warning(startup_warnings: &mut Vec) {} +fn push_missing_system_bwrap_startup_warning(_startup_warnings: &mut Vec) {} fn resolve_sqlite_home_env(resolved_cwd: &Path) -> Option { let raw = std::env::var(codex_state::SQLITE_HOME_ENV).ok()?; @@ -2100,6 +2098,7 @@ impl Config { let user_instructions = Self::load_instructions(Some(&codex_home)); let mut startup_warnings = Vec::new(); + push_missing_system_bwrap_startup_warning(&mut startup_warnings); // Destructure ConfigOverrides fully to ensure all overrides are applied. let ConfigOverrides { diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index 96bbfb887de7..fdaca4b1f977 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -52,7 +52,6 @@ use codex_core::config::ConfigBuilder; use codex_core::config::ConfigOverrides; use codex_core::config::find_codex_home; use codex_core::config::load_config_as_toml_with_cli_overrides; -use codex_core::config::push_missing_system_bwrap_startup_warning; use codex_core::config::resolve_oss_provider; use codex_core::config_loader::ConfigLoadError; use codex_core::config_loader::LoaderOverrides; @@ -361,13 +360,12 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result additional_writable_roots: add_dir, }; - let mut config = ConfigBuilder::default() + let config = ConfigBuilder::default() .cli_overrides(cli_kv_overrides) .harness_overrides(overrides) .cloud_requirements(cloud_requirements) .build() .await?; - push_missing_system_bwrap_startup_warning(&mut config.startup_warnings); #[allow(clippy::print_stderr)] match check_execpolicy_for_warnings(&config.config_layer_stack).await { diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index 186026b20a0e..f537c95bbc03 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -21,7 +21,6 @@ use codex_core::config::ConfigBuilder; use codex_core::config::ConfigOverrides; use codex_core::config::find_codex_home; use codex_core::config::load_config_as_toml_with_cli_overrides; -use codex_core::config::push_missing_system_bwrap_startup_warning; use codex_core::config::resolve_oss_provider; use codex_core::config_loader::CloudRequirementsLoader; use codex_core::config_loader::ConfigLoadError; @@ -1177,10 +1176,7 @@ async fn load_config_or_exit_with_fallback_cwd( .build() .await { - Ok(mut config) => { - push_missing_system_bwrap_startup_warning(&mut config.startup_warnings); - config - } + Ok(config) => config, Err(err) => { eprintln!("Error loading configuration: {err}"); std::process::exit(1); diff --git a/codex-rs/tui_app_server/src/lib.rs b/codex-rs/tui_app_server/src/lib.rs index 78c22a7e0be5..703049063438 100644 --- a/codex-rs/tui_app_server/src/lib.rs +++ b/codex-rs/tui_app_server/src/lib.rs @@ -28,7 +28,6 @@ use codex_core::config::ConfigBuilder; use codex_core::config::ConfigOverrides; use codex_core::config::find_codex_home; use codex_core::config::load_config_as_toml_with_cli_overrides; -use codex_core::config::push_missing_system_bwrap_startup_warning; use codex_core::config::resolve_oss_provider; use codex_core::config_loader::CloudRequirementsLoader; use codex_core::config_loader::ConfigLoadError; @@ -399,8 +398,6 @@ where F: FnOnce(InProcessClientStartArgs) -> Fut, Fut: Future>, { - push_missing_system_bwrap_startup_warning(&mut config.startup_warnings); - let config_warnings = config .startup_warnings .iter() @@ -1514,10 +1511,7 @@ async fn load_config_or_exit_with_fallback_cwd( .build() .await { - Ok(mut config) => { - push_missing_system_bwrap_startup_warning(&mut config.startup_warnings); - config - } + Ok(config) => config, Err(err) => { eprintln!("Error loading configuration: {err}"); std::process::exit(1); From b015bafe8295a116111af370b74b92d129cd3b47 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Tue, 17 Mar 2026 13:16:50 -0700 Subject: [PATCH 10/14] refactor(linux-sandbox): inline bwrap startup warning --- codex-rs/core/src/config/mod.rs | 24 ++++++------------------ codex-rs/tui_app_server/src/lib.rs | 2 +- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 6f437de20f65..cc91c45661f6 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -148,23 +148,6 @@ const RESERVED_MODEL_PROVIDER_IDS: [&str; 3] = [ LMSTUDIO_OSS_PROVIDER_ID, ]; -#[cfg(target_os = "linux")] -fn push_missing_system_bwrap_startup_warning(startup_warnings: &mut Vec) { - if Path::new(SYSTEM_BWRAP_PATH).is_file() { - return; - } - - let message = format!( - "Codex could not find system bubblewrap at {SYSTEM_BWRAP_PATH}. Please install bubblewrap with your package manager. Codex will use the vendored bubblewrap in the meantime." - ); - if !startup_warnings.contains(&message) { - startup_warnings.push(message); - } -} - -#[cfg(not(target_os = "linux"))] -fn push_missing_system_bwrap_startup_warning(_startup_warnings: &mut Vec) {} - fn resolve_sqlite_home_env(resolved_cwd: &Path) -> Option { let raw = std::env::var(codex_state::SQLITE_HOME_ENV).ok()?; let trimmed = raw.trim(); @@ -2098,7 +2081,12 @@ impl Config { let user_instructions = Self::load_instructions(Some(&codex_home)); let mut startup_warnings = Vec::new(); - push_missing_system_bwrap_startup_warning(&mut startup_warnings); + #[cfg(target_os = "linux")] + if !Path::new(SYSTEM_BWRAP_PATH).is_file() { + startup_warnings.push(format!( + "Codex could not find system bubblewrap at {SYSTEM_BWRAP_PATH}. Please install bubblewrap with your package manager. Codex will use the vendored bubblewrap in the meantime." + )); + } // Destructure ConfigOverrides fully to ensure all overrides are applied. let ConfigOverrides { diff --git a/codex-rs/tui_app_server/src/lib.rs b/codex-rs/tui_app_server/src/lib.rs index 703049063438..0546d88487a3 100644 --- a/codex-rs/tui_app_server/src/lib.rs +++ b/codex-rs/tui_app_server/src/lib.rs @@ -387,7 +387,7 @@ pub(crate) async fn start_embedded_app_server_for_picker( async fn start_embedded_app_server_with( arg0_paths: Arg0DispatchPaths, - mut config: Config, + config: Config, cli_kv_overrides: Vec<(String, toml::Value)>, loader_overrides: LoaderOverrides, cloud_requirements: CloudRequirementsLoader, From 00edde7587f9641551a42e67fd0b2c90563bd134 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Tue, 17 Mar 2026 13:25:18 -0700 Subject: [PATCH 11/14] refactor(linux-sandbox): reorder bwrap startup warning --- codex-rs/core/src/config/mod.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index cc91c45661f6..75247b44f30a 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -2081,12 +2081,6 @@ impl Config { let user_instructions = Self::load_instructions(Some(&codex_home)); let mut startup_warnings = Vec::new(); - #[cfg(target_os = "linux")] - if !Path::new(SYSTEM_BWRAP_PATH).is_file() { - startup_warnings.push(format!( - "Codex could not find system bubblewrap at {SYSTEM_BWRAP_PATH}. Please install bubblewrap with your package manager. Codex will use the vendored bubblewrap in the meantime." - )); - } // Destructure ConfigOverrides fully to ensure all overrides are applied. let ConfigOverrides { @@ -2333,6 +2327,12 @@ impl Config { )); } } + #[cfg(target_os = "linux")] + if !Path::new(SYSTEM_BWRAP_PATH).is_file() { + startup_warnings.push(format!( + "Codex could not find system bubblewrap at {SYSTEM_BWRAP_PATH}. Please install bubblewrap with your package manager. Codex will use the vendored bubblewrap in the meantime." + )); + } let effective_openai_base_url = openai_base_url.or(openai_base_url_from_env); let mut model_providers = built_in_model_providers(effective_openai_base_url); From 225434b5fa31ee7dfb367b847ca33d9cd008d623 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Tue, 17 Mar 2026 14:09:18 -0700 Subject: [PATCH 12/14] fix(linux-sandbox): scope bwrap warning to startup surfaces --- codex-rs/app-server/src/lib.rs | 8 ++++++++ codex-rs/core/src/config/config_tests.rs | 26 +++++------------------- codex-rs/core/src/config/mod.rs | 22 ++++++++++++++------ codex-rs/exec/src/lib.rs | 6 ++++++ codex-rs/tui/src/app.rs | 11 ++++++++++ codex-rs/tui_app_server/src/app.rs | 11 ++++++++++ 6 files changed, 57 insertions(+), 27 deletions(-) diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index 4bec5b3c9dd3..85804098bdbe 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -477,6 +477,14 @@ pub async fn run_main_with_transport( range: None, }); } + if let Some(warning) = codex_core::config::missing_system_bwrap_warning() { + config_warnings.push(ConfigWarningNotification { + summary: warning, + details: None, + path: None, + range: None, + }); + } let feedback = CodexFeedback::new(); diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index cd0f105c5a1a..c28ec6eeba32 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -5497,32 +5497,16 @@ shell_tool = true Ok(()) } -#[tokio::test] -async fn missing_system_bwrap_warning_is_added_during_config_load() -> std::io::Result<()> { - let codex_home = TempDir::new()?; - - let config = ConfigBuilder::default() - .codex_home(codex_home.path().to_path_buf()) - .fallback_cwd(Some(codex_home.path().to_path_buf())) - .build() - .await?; - - let has_warning = config.startup_warnings.iter().any(|warning| { - warning.contains("Codex could not find system bubblewrap at /usr/bin/bwrap") - }); - +#[test] +fn missing_system_bwrap_warning_matches_system_bwrap_presence() { #[cfg(target_os = "linux")] assert_eq!( - has_warning, - !Path::new("/usr/bin/bwrap").is_file(), - "{:?}", - config.startup_warnings + missing_system_bwrap_warning().is_some(), + !Path::new("/usr/bin/bwrap").is_file() ); #[cfg(not(target_os = "linux"))] - assert!(!has_warning, "{:?}", config.startup_warnings); - - Ok(()) + assert!(missing_system_bwrap_warning().is_none()); } #[tokio::test] diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 75247b44f30a..d6ccfbeb4fd4 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -148,6 +148,22 @@ const RESERVED_MODEL_PROVIDER_IDS: [&str; 3] = [ LMSTUDIO_OSS_PROVIDER_ID, ]; +#[cfg(target_os = "linux")] +pub fn missing_system_bwrap_warning() -> Option { + if Path::new(SYSTEM_BWRAP_PATH).is_file() { + None + } else { + Some(format!( + "Codex could not find system bubblewrap at {SYSTEM_BWRAP_PATH}. Please install bubblewrap with your package manager. Codex will use the vendored bubblewrap in the meantime." + )) + } +} + +#[cfg(not(target_os = "linux"))] +pub fn missing_system_bwrap_warning() -> Option { + None +} + fn resolve_sqlite_home_env(resolved_cwd: &Path) -> Option { let raw = std::env::var(codex_state::SQLITE_HOME_ENV).ok()?; let trimmed = raw.trim(); @@ -2327,12 +2343,6 @@ impl Config { )); } } - #[cfg(target_os = "linux")] - if !Path::new(SYSTEM_BWRAP_PATH).is_file() { - startup_warnings.push(format!( - "Codex could not find system bubblewrap at {SYSTEM_BWRAP_PATH}. Please install bubblewrap with your package manager. Codex will use the vendored bubblewrap in the meantime." - )); - } let effective_openai_base_url = openai_base_url.or(openai_base_url_from_env); let mut model_providers = built_in_model_providers(effective_openai_base_url); diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index fdaca4b1f977..5fe782843560 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -663,6 +663,12 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> { // Print the effective configuration and initial request so users can see what Codex // is using. event_processor.print_config_summary(&config, &prompt_summary, &session_configured); + if let Some(message) = codex_core::config::missing_system_bwrap_warning() { + let _ = event_processor.process_event(Event { + id: String::new(), + msg: EventMsg::Warning(codex_protocol::protocol::WarningEvent { message }), + }); + } info!("Codex initialized with event: {session_configured:?}"); diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index d79b36601436..56fd66f06ed4 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -275,6 +275,16 @@ fn emit_project_config_warnings(app_event_tx: &AppEventSender, config: &Config) ))); } +fn emit_missing_system_bwrap_warning(app_event_tx: &AppEventSender) { + let Some(message) = codex_core::config::missing_system_bwrap_warning() else { + return; + }; + + app_event_tx.send(AppEvent::InsertHistoryCell(Box::new( + history_cell::new_warning_event(message), + ))); +} + #[derive(Debug, Clone, PartialEq, Eq)] struct SessionSummary { usage_line: String, @@ -1963,6 +1973,7 @@ impl App { let (app_event_tx, mut app_event_rx) = unbounded_channel(); let app_event_tx = AppEventSender::new(app_event_tx); emit_project_config_warnings(&app_event_tx, &config); + emit_missing_system_bwrap_warning(&app_event_tx); tui.set_notification_method(config.tui_notification_method); let harness_overrides = diff --git a/codex-rs/tui_app_server/src/app.rs b/codex-rs/tui_app_server/src/app.rs index 9fd3f1bd3dd5..9236af5fce09 100644 --- a/codex-rs/tui_app_server/src/app.rs +++ b/codex-rs/tui_app_server/src/app.rs @@ -276,6 +276,16 @@ fn emit_project_config_warnings(app_event_tx: &AppEventSender, config: &Config) ))); } +fn emit_missing_system_bwrap_warning(app_event_tx: &AppEventSender) { + let Some(message) = codex_core::config::missing_system_bwrap_warning() else { + return; + }; + + app_event_tx.send(AppEvent::InsertHistoryCell(Box::new( + history_cell::new_warning_event(message), + ))); +} + #[derive(Debug, Clone, PartialEq, Eq)] struct SessionSummary { usage_line: String, @@ -2246,6 +2256,7 @@ impl App { let (app_event_tx, mut app_event_rx) = unbounded_channel(); let app_event_tx = AppEventSender::new(app_event_tx); emit_project_config_warnings(&app_event_tx, &config); + emit_missing_system_bwrap_warning(&app_event_tx); tui.set_notification_method(config.tui_notification_method); let harness_overrides = From ee4c028a1558ad82112b89dae345be6559b188c0 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Tue, 17 Mar 2026 14:17:10 -0700 Subject: [PATCH 13/14] fix(linux-sandbox): avoid expect in launcher --- codex-rs/linux-sandbox/src/launcher.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/codex-rs/linux-sandbox/src/launcher.rs b/codex-rs/linux-sandbox/src/launcher.rs index 70ce8e15bfa8..37a860e085fd 100644 --- a/codex-rs/linux-sandbox/src/launcher.rs +++ b/codex-rs/linux-sandbox/src/launcher.rs @@ -24,13 +24,15 @@ pub(crate) fn exec_bwrap(argv: Vec, preserved_files: Vec) -> ! { } fn preferred_bwrap_launcher() -> BubblewrapLauncher { - let system_bwrap_path = AbsolutePathBuf::from_absolute_path(Path::new(SYSTEM_BWRAP_PATH)) - .expect("system bubblewrap path should be absolute"); - if system_bwrap_path.as_path().is_file() { - BubblewrapLauncher::System(system_bwrap_path) - } else { - BubblewrapLauncher::Vendored + if !Path::new(SYSTEM_BWRAP_PATH).is_file() { + return BubblewrapLauncher::Vendored; } + + let system_bwrap_path = match AbsolutePathBuf::from_absolute_path(SYSTEM_BWRAP_PATH) { + Ok(path) => path, + Err(err) => panic!("failed to normalize system bubblewrap path {SYSTEM_BWRAP_PATH}: {err}"), + }; + BubblewrapLauncher::System(system_bwrap_path) } fn exec_system_bwrap( From 00b7d98ea36d4ff753666ea8e6be1f1d22830bb1 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Tue, 17 Mar 2026 14:37:29 -0700 Subject: [PATCH 14/14] fix(exec): suppress bwrap warning in json mode --- codex-rs/exec/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index 5fe782843560..d27cec1f57fc 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -663,7 +663,7 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> { // Print the effective configuration and initial request so users can see what Codex // is using. event_processor.print_config_summary(&config, &prompt_summary, &session_configured); - if let Some(message) = codex_core::config::missing_system_bwrap_warning() { + if !json_mode && let Some(message) = codex_core::config::missing_system_bwrap_warning() { let _ = event_processor.process_event(Event { id: String::new(), msg: EventMsg::Warning(codex_protocol::protocol::WarningEvent { message }),