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
This commit is contained in:
jif
2026-10-13 19:36:44 +00:00
committed by copyberry
parent 5d4922595a
commit e762c05bd6
8 changed files with 157 additions and 10 deletions
+40 -1
View File
@@ -1308,7 +1308,46 @@ async fn cli_main(
println!("{}", serde_json::to_string(&output)?);
}
AppServerDaemonSubcommand::Restart => {
print_app_server_daemon_output(AppServerLifecycleCommand::Restart).await?;
let overrides = root_config_overrides
.parse_overrides()
.map_err(anyhow::Error::msg)?;
let layer = codex_config::build_cli_overrides_layer(&overrides);
let features = layer
.get("features")
.and_then(toml::Value::as_table)
.into_iter()
.flatten()
.map(|(name, value)| (name.as_str(), value))
.collect::<std::collections::BTreeMap<_, _>>();
let mut feature_overrides = std::collections::BTreeMap::new();
// Match Features::apply_map's alias precedence.
for (&name, &value) in &features {
if name == "imagegenext" && features.contains_key("image_generation") {
continue;
}
let value = if let Some(table) = value.as_table() {
let Some(enabled) = table.get("enabled") else {
continue;
};
enabled
} else {
value
};
let feature =
codex_features::feature_for_key(name).ok_or_else(|| {
anyhow::anyhow!(
"daemon restart requires a boolean feature toggle: {name}"
)
})?;
let enabled = value.as_bool().ok_or_else(|| {
anyhow::anyhow!("daemon restart requires a boolean enablement for features.{name}")
})?;
feature_overrides.insert(feature.key().to_string(), enabled);
}
print_app_server_daemon_output(AppServerLifecycleCommand::Restart {
feature_overrides,
})
.await?;
}
AppServerDaemonSubcommand::Update {
from_cli: true,