From 8f82b8a31cfdf690479067a179eba2c761102643 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Sun, 4 Oct 2026 06:19:24 +0000 Subject: [PATCH] Use the managed daemon for eligible remote-control launches (#50803) ## What changed Make plain `codex remote-control` start or reuse the managed daemon and enable remote control when daemon auto-start is enabled and the environment is eligible. Fall back to the foreground server when no managed backend is available or Windows restricts detached launch. Add `codex remote-control --no-daemon` to explicitly run the foreground server, and honor the top-level `--no-daemon` flag. Reject this flag with `start`, `stop`, or `pair`. Keep `--json` and launches with configuration overrides or unsupported environments on the foreground path. GitOrigin-RevId: cb28ae3f6074993b748660972c3cb7cbf6612c2f --- codex-rs/app-server-daemon/README.md | 7 ++- codex-rs/cli/src/main.rs | 3 +- codex-rs/cli/src/remote_control_cmd.rs | 83 ++++++++++++++++++++++++-- codex-rs/tui/src/daemon_startup.rs | 3 +- codex-rs/tui/src/lib.rs | 1 + 5 files changed, 86 insertions(+), 11 deletions(-) diff --git a/codex-rs/app-server-daemon/README.md b/codex-rs/app-server-daemon/README.md index 74721c8b8d..b8fc956bcc 100644 --- a/codex-rs/app-server-daemon/README.md +++ b/codex-rs/app-server-daemon/README.md @@ -169,9 +169,10 @@ so the new setting takes effect immediately. Top-level `codex remote-control start` enables and persists remote control for the managed daemon, overriding a saved disabled value. It starts or bootstraps -the daemon as needed. Plain `codex remote-control` runs a separate foreground -server and does not change daemon settings; `codex remote-control stop` stops -the managed daemon without clearing its saved remote-control preference. +the daemon as needed. Plain `codex remote-control` uses the managed daemon when +eligible; `codex remote-control --no-daemon` runs a separate foreground server. +`codex remote-control stop` stops the managed daemon without clearing its saved +remote-control preference. `daemon start` and `daemon restart` use that saved preference. `daemon bootstrap` sets it according to `--remote-control` (disabled when omitted). diff --git a/codex-rs/cli/src/main.rs b/codex-rs/cli/src/main.rs index c047b20927..8d062a2397 100644 --- a/codex-rs/cli/src/main.rs +++ b/codex-rs/cli/src/main.rs @@ -1429,7 +1429,8 @@ async fn cli_main( } } } - Some(Subcommand::RemoteControl(remote_control_cli)) => { + Some(Subcommand::RemoteControl(mut remote_control_cli)) => { + remote_control_cli.no_daemon |= interactive.no_daemon; let subcommand_name = remote_control_cli.subcommand_name(); reject_remote_mode_for_subcommand( root_remote.as_deref(), diff --git a/codex-rs/cli/src/remote_control_cmd.rs b/codex-rs/cli/src/remote_control_cmd.rs index b478ad0759..21cff3a7a6 100644 --- a/codex-rs/cli/src/remote_control_cmd.rs +++ b/codex-rs/cli/src/remote_control_cmd.rs @@ -15,6 +15,8 @@ use codex_app_server_protocol::RemoteControlConnectionStatus; use codex_app_server_protocol::RemoteControlPairingStartResponse; use codex_arg0::Arg0DispatchPaths; use codex_config::LoaderOverrides; +use codex_core::config::ConfigBuilder; +use codex_features::Feature; use codex_protocol::protocol::SessionSource; use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_cli::CliConfigOverrides; @@ -34,8 +36,12 @@ pub(crate) struct RemoteControlCommand { #[arg(long = "json", global = true)] json: bool, + /// Run an embedded app-server in the foreground until Ctrl-C. + #[arg(long = "no-daemon")] + pub(super) no_daemon: bool, + #[command(subcommand)] - subcommand: Option, + pub(super) subcommand: Option, } impl RemoteControlCommand { @@ -50,7 +56,7 @@ impl RemoteControlCommand { } #[derive(Debug, Clone, Copy, clap::Subcommand)] -enum RemoteControlSubcommand { +pub(super) enum RemoteControlSubcommand { /// Start the app-server daemon with remote control enabled. Start, @@ -66,14 +72,57 @@ pub(crate) async fn run( arg0_paths: Arg0DispatchPaths, root_config_overrides: CliConfigOverrides, ) -> anyhow::Result<()> { - match command.subcommand { - None => { + if command.no_daemon && command.subcommand.is_some() { + anyhow::bail!("`--no-daemon` cannot be used with a remote-control subcommand"); + } + + if command.subcommand.is_none() { + if command.no_daemon || command.json || !daemon_eligible(&root_config_overrides).await? { + run_foreground_remote_control(command.json, arg0_paths, root_config_overrides).await?; + } else { print_remote_control_progress( command.json, - "Starting app-server with remote control enabled...", + "Starting app-server daemon with remote control enabled...", )?; - run_foreground_remote_control(command.json, arg0_paths, root_config_overrides).await?; + let daemon = match codex_app_server_daemon::start_with_features(&Default::default()) + .await + { + Ok(daemon) => daemon, + #[cfg(windows)] + Err(error) if error.is::() => { + return run_foreground_remote_control( + command.json, + arg0_paths, + root_config_overrides, + ) + .await; + } + Err(error) => return Err(error), + }; + if daemon.backend.is_none() { + return run_foreground_remote_control( + command.json, + arg0_paths, + root_config_overrides, + ) + .await; + } + let remote_control = codex_app_server_daemon::enable_remote_control_on_socket( + &daemon.socket_path, + FOREGROUND_SOCKET_CONNECT_TIMEOUT, + FOREGROUND_SOCKET_CONNECT_RETRY_DELAY, + ) + .await?; + let output = AppServerRemoteControlReadyOutput { + daemon: AppServerRemoteControlStartOutput::Start(daemon), + remote_control, + }; + print_remote_control_start_output(&output, command.json)?; } + return Ok(()); + } + + match command.subcommand { Some(RemoteControlSubcommand::Start) => { print_remote_control_progress( command.json, @@ -91,10 +140,31 @@ pub(crate) async fn run( let output = codex_app_server_daemon::start_remote_control_pairing().await?; print_remote_control_pairing_output(&output, command.json)?; } + None => unreachable!("bare remote-control returned before explicit subcommand dispatch"), } Ok(()) } +async fn daemon_eligible(root_config_overrides: &CliConfigOverrides) -> anyhow::Result { + if !root_config_overrides.raw_overrides.is_empty() + || codex_login::is_workload_identity_selected() + || std::env::var_os(codex_exec_server::CODEX_EXEC_SERVER_URL_ENV_VAR).is_some() + { + return Ok(false); + } + + #[cfg(windows)] + if codex_app_server_daemon::is_elevated()? { + return Ok(false); + } + + let Ok(config) = ConfigBuilder::default().build().await else { + return Ok(false); + }; + Ok(config.features.enabled(Feature::DaemonAutoStart) + && !codex_tui::uses_wsl_drvfs(&config.codex_home)) +} + fn print_remote_control_progress(json: bool, message: &str) -> anyhow::Result<()> { if json { return Ok(()); @@ -112,6 +182,7 @@ async fn run_foreground_remote_control( arg0_paths: Arg0DispatchPaths, root_config_overrides: CliConfigOverrides, ) -> anyhow::Result<()> { + print_remote_control_progress(json, "Starting app-server with remote control enabled...")?; #[cfg(not(windows))] let socket_dir = tempfile::Builder::new() .prefix("codex-rc-") diff --git a/codex-rs/tui/src/daemon_startup.rs b/codex-rs/tui/src/daemon_startup.rs index 98cf9a5429..6476c94c7b 100644 --- a/codex-rs/tui/src/daemon_startup.rs +++ b/codex-rs/tui/src/daemon_startup.rs @@ -17,7 +17,8 @@ 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 { +/// Returns whether `codex_home` is on a Windows-mounted WSL filesystem. +pub 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. diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index 001c3aac68..75f43930e2 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -75,6 +75,7 @@ use codex_utils_oss::get_default_model_for_oss_provider; use color_eyre::eyre::WrapErr; use crossterm::SynchronizedUpdate; use cwd_prompt::CwdPromptAction; +pub use daemon_startup::uses_wsl_drvfs; pub use session_archive_commands::DeleteConfirmation; pub use session_archive_commands::SessionArchiveAction; pub use session_archive_commands::SessionArchiveCommandOptions;