Files
codex/codex-rs/app-server-daemon/src/telemetry.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

79 lines
2.5 KiB
Rust

//! Bounded observations for foreground callers. This module never exports telemetry;
//! in particular, the detached updater must not create a telemetry provider.
use std::path::Path;
/// Failure of a foreground auto-start, with the original diagnostic kept separate from the metric.
#[derive(Debug)]
pub struct StartError {
pub error: anyhow::Error,
during_install: bool,
}
impl StartError {
pub fn failure_reason(&self) -> &'static str {
#[cfg(windows)]
if self.error.is::<crate::DetachedLaunchRestricted>() {
return "spawn";
}
match self.error.downcast_ref::<StartFailure>() {
Some(StartFailure::Spawn(_)) => "spawn",
Some(StartFailure::EarlyChildExit(_)) => "early_child_exit",
Some(StartFailure::ReadinessTimeout(_)) => "readiness_timeout",
None if self.during_install => "install",
None => "other",
}
}
pub(crate) fn install(error: anyhow::Error) -> Self {
Self {
error,
during_install: true,
}
}
}
impl From<anyhow::Error> for StartError {
fn from(error: anyhow::Error) -> Self {
Self {
error,
during_install: false,
}
}
}
/// Typed anyhow context preserves both the original diagnostic and downcasts through it.
#[derive(Debug)]
pub(crate) enum StartFailure {
Spawn(String),
EarlyChildExit(String),
ReadinessTimeout(String),
}
impl std::fmt::Display for StartFailure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Spawn(message)
| Self::EarlyChildExit(message)
| Self::ReadinessTimeout(message) => f.write_str(message),
}
}
}
/// Suppress child reporting when the foreground TUI handoff owns the observation.
pub const HANDOFF_ENV: &str = "CODEX_DAEMON_TELEMETRY_HANDOFF";
/// Only presence and enabled state leave the settings reader, never values or contents.
/// Missing files mean defaults; unreadable or invalid settings remain unknown.
pub async fn settings_tags(codex_home: &Path) -> [(&'static str, &'static str); 4] {
let path = codex_home
.join(crate::STATE_DIR_NAME)
.join(crate::SETTINGS_FILE_NAME);
crate::settings::telemetry_tags(&path).await.unwrap_or([
("auto_update", "unknown"),
("auto_update_setting", "unknown"),
("update_interval_setting", "unknown"),
("shutdown_grace_setting", "unknown"),
])
}