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
This commit is contained in:
Eric Traut
2026-10-04 06:28:20 +00:00
committed by copyberry
parent b989f795b1
commit 8f82b8a31c
5 changed files with 86 additions and 11 deletions
+4 -3
View File
@@ -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).
+2 -1
View File
@@ -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(),
+77 -6
View File
@@ -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<RemoteControlSubcommand>,
pub(super) subcommand: Option<RemoteControlSubcommand>,
}
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::<codex_app_server_daemon::DetachedLaunchRestricted>() => {
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<bool> {
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-")
+2 -1
View File
@@ -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.
+1
View File
@@ -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;