Files
codex/codex-rs/app-server-daemon/src/launch.rs
T
jif e762c05bd6 Apply feature overrides when restarting the app-server daemon (#53751)
## Why

`codex app-server daemon restart` previously reused saved settings without applying feature flags supplied on the command line.

## What changed

Apply `--enable`, `--disable`, and boolean `-c features.*` overrides during an explicit restart and persist them for subsequent restarts. Accept nested `enabled` values, normalize feature aliases, and replace conflicting saved aliases while preserving unrelated settings.

Reject invalid feature toggles before stopping the daemon or saving settings.

## Testing

Add integration coverage for overrides on stopped and running daemons, alias replacement, preservation of saved settings, restarts without flags, and rejection of non-toggle keys without interrupting the running daemon.

GitOrigin-RevId: 14a657bc4758b9c248e363f9dac58f8887070a04
2026-10-13 19:36:44 +00:00

75 lines
3.1 KiB
Rust

//! Invocation overrides apply on fresh starts or explicitly confirmed managed restarts.
//! The lifecycle lock serializes persistence against other clients and updates.
use crate::Daemon;
use crate::LifecycleOutput;
use crate::ensure_supported_platform;
use anyhow::Result;
use anyhow::ensure;
use std::collections::BTreeMap;
/// Start a missing daemon with these features, preserving other saved overrides,
/// or leave a running daemon unchanged.
/// Callers must check the running server's configuration before using it.
/// Startup diagnostics go to tracing, so a caller with a live TUI can keep rendering.
pub async fn start_with_features(features: &BTreeMap<String, bool>) -> Result<LifecycleOutput> {
ensure_supported_platform()?;
#[cfg(windows)]
crate::backend::windows::ensure_not_elevated()?;
let mut daemon = Daemon::from_environment()?;
daemon.log_diagnostics = true;
let _operation_lock = daemon.acquire_operation_lock().await?;
let selected = daemon.current_installation()?;
let mut overrides = selected.load_settings().await?.feature_overrides;
overrides.extend(features.clone());
Box::pin(selected.start(&overrides)).await
}
/// Apply confirmed feature settings to an existing managed daemon and restart it.
/// Callers must obtain consent for changes to shared services and interrupted work,
/// then verify effective server compatibility after this operation completes.
pub async fn restart_with_features(features: &BTreeMap<String, bool>) -> Result<LifecycleOutput> {
ensure_supported_platform()?;
#[cfg(windows)]
crate::backend::windows::ensure_not_elevated()?;
let daemon = Daemon::from_environment()?;
let _operation_lock = daemon.acquire_operation_lock().await?;
let selected = daemon.current_installation()?;
Box::pin(selected.restart_with_features_locked(features)).await
}
impl Daemon {
pub(super) async fn restart(
&self,
features: &BTreeMap<String, bool>,
) -> Result<LifecycleOutput> {
let mut settings = self.load_settings().await?;
// Explicit requests replace saved aliases as well as canonical keys.
settings.feature_overrides.retain(|name, _| {
let canonical = codex_features::feature_for_key(name)
.map(codex_features::Feature::key)
.unwrap_or(name);
!features.contains_key(canonical)
});
settings.feature_overrides.extend(features.clone());
self.restart_with_settings(settings).await
}
pub(super) async fn restart_with_features_locked(
&self,
features: &BTreeMap<String, bool>,
) -> Result<LifecycleOutput> {
let mut settings = self.load_settings().await?;
ensure!(
self.running_backend_instance(&settings).await?.is_some(),
"no running managed daemon; rerun the command to check the current server"
);
let previous = settings.feature_overrides.clone();
settings.feature_overrides.extend(features.clone());
if settings.feature_overrides == previous {
return self.start(&previous).await;
}
self.restart_with_settings(settings).await
}
}