diff --git a/codex-rs/tui/src/daemon_startup.rs b/codex-rs/tui/src/daemon_startup.rs index 0831d92e3c..98cf9a5429 100644 --- a/codex-rs/tui/src/daemon_startup.rs +++ b/codex-rs/tui/src/daemon_startup.rs @@ -15,6 +15,37 @@ const SERVER_FEATURES: [Feature; 4] = [ ]; pub(super) const FAILURE_HINT: &str = "To work without the background server, rerun the same command with --no-daemon (including resume or fork and its arguments)."; +pub(super) const WSL_DRVFS_EXCLUSION: &str = "a Windows-mounted WSL CODEX_HOME (DrvFS/9p)"; + +pub(super) fn uses_wsl_drvfs(codex_home: &std::path::Path) -> bool { + // The managed daemon writes an executable and Unix-style state beneath CODEX_HOME. + // DrvFS does not reliably support the required permission semantics, so starting it + // there can fail before the TUI opens. + #[cfg(target_os = "linux")] + { + use std::os::fd::AsRawFd; + + if !codex_utils_path::is_wsl() && std::env::var_os("WSL_INTEROP").is_none() { + return false; + } + let Some(directory) = codex_home + .ancestors() + .find_map(|path| std::fs::File::open(path).ok()) + else { + return false; + }; + let mut stats = std::mem::MaybeUninit::::uninit(); + if unsafe { libc::fstatfs(directory.as_raw_fd(), stats.as_mut_ptr()) } != 0 { + return false; + } + unsafe { stats.assume_init() }.f_type as u64 == 0x0102_1997 + } + #[cfg(not(target_os = "linux"))] + { + let _ = codex_home; + false + } +} #[cfg(any(windows, test))] pub(super) const ELEVATED_LAUNCH_WARNING: &str = "Running as administrator: shared background server disabled. To enable it, restart Codex in a terminal without administrator permissions."; diff --git a/codex-rs/tui/src/daemon_startup_tests.rs b/codex-rs/tui/src/daemon_startup_tests.rs index 8d0aa63535..93f169f037 100644 --- a/codex-rs/tui/src/daemon_startup_tests.rs +++ b/codex-rs/tui/src/daemon_startup_tests.rs @@ -431,6 +431,10 @@ fn daemon_exclusion_warning_snapshot() { let cell = crate::history_cell::StartupWarningsCell::new(vec![ "Running without the shared background server: --strict-config requires embedded mode." .into(), + format!( + "Running without the shared background server: {} requires embedded mode.", + daemon_startup::WSL_DRVFS_EXCLUSION + ), ]); let text = cell .transcript_lines(/*width*/ 80) diff --git a/codex-rs/tui/src/snapshots/codex_tui__daemon_startup_tests__daemon_exclusion_warning.snap b/codex-rs/tui/src/snapshots/codex_tui__daemon_startup_tests__daemon_exclusion_warning.snap index 0dce819cdf..6e3a97b527 100644 --- a/codex-rs/tui/src/snapshots/codex_tui__daemon_startup_tests__daemon_exclusion_warning.snap +++ b/codex-rs/tui/src/snapshots/codex_tui__daemon_startup_tests__daemon_exclusion_warning.snap @@ -4,3 +4,5 @@ expression: text --- ⚠ Running without the shared background server: --strict-config requires embedded mode. +⚠ Running without the shared background server: a Windows-mounted WSL CODEX_HOME + (DrvFS/9p) requires embedded mode. diff --git a/codex-rs/tui/src/startup_orchestration.rs b/codex-rs/tui/src/startup_orchestration.rs index 3cc8dc18d2..87c1c42f54 100644 --- a/codex-rs/tui/src/startup_orchestration.rs +++ b/codex-rs/tui/src/startup_orchestration.rs @@ -533,6 +533,13 @@ pub(super) async fn run_main_inner( daemon_exclusion = Some("Bedrock sign-in"); app_server_target = AppServerTarget::Embedded; } + if auto_start_daemon + && daemon_exclusion.is_none() + && matches!(&app_server_target, AppServerTarget::Embedded) + && daemon_startup::uses_wsl_drvfs(&codex_home) + { + daemon_exclusion = Some(daemon_startup::WSL_DRVFS_EXCLUSION); + } let mut daemon_features = daemon_startup::server_features(&cli_kv_overrides); // Disabling shared services requires confirmation, even on a fresh auto-start. daemon_features.retain(|_, enabled| *enabled);