Files
codex/codex-rs/app-server-daemon/src/launch.rs
T
Eric Traut dbaead0106 Classify daemon auto-start failures in TUI telemetry (#59058)
## What changed

Add a `failure_reason` tag to failed TUI auto-start events in `codex.daemon.start`, distinguishing `install`, `spawn`, `early_child_exit`, `readiness_timeout`, and `other` failures. Preserve the original error diagnostics through a classified startup API.

Detect managed child exits during process identity recording and distinguish exits before readiness from readiness timeouts.

## Testing

Extend daemon tests to assert classification of installation failures, spawn failures, early child exits, and readiness timeouts.

GitOrigin-RevId: 7c626f278e9e7f2ecd61b3fd1a90829a113eb232
2026-11-03 04:20:50 +00:00

85 lines
3.5 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 crate::telemetry::StartError;
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> {
start_with_features_classified(features)
.await
.map_err(|err| err.error)
}
/// Starts the daemon and classifies failures for the TUI startup counter.
pub async fn start_with_features_classified(
features: &BTreeMap<String, bool>,
) -> Result<LifecycleOutput, StartError> {
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.map_err(|err| err.error);
}
self.restart_with_settings(settings).await
}
}