From bde734fd1e2e82f3417606bb7c3ad105101fcf8a Mon Sep 17 00:00:00 2001 From: Owen Lin Date: Tue, 13 Jan 2026 11:59:39 -0800 Subject: [PATCH] feat(app-server): add an --analytics-default-enabled flag (#9118) Add a new `codex app-server --analytics-default-enabled` CLI flag that controls whether analytics are enabled by default. Analytics are disabled by default for app-server. Users have to explicitly opt in via the `analytics` section in the config.toml file. However, for first-party use cases like the VSCode IDE extension, we default analytics to be enabled by default by setting this flag. Users can still opt out by setting this in their config.toml: ```toml [analytics] enabled = false ``` See https://developers.openai.com/codex/config-advanced/#metrics for more details. --- codex-rs/app-server/src/lib.rs | 3 +- codex-rs/app-server/src/main.rs | 1 + .../app-server/tests/suite/v2/analytics.rs | 66 +++++++++++++++++++ codex-rs/app-server/tests/suite/v2/mod.rs | 1 + codex-rs/cli/src/main.rs | 40 +++++++++++ 5 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 codex-rs/app-server/tests/suite/v2/analytics.rs diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index 68739c008..d9aaabd1c 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -44,6 +44,7 @@ pub async fn run_main( codex_linux_sandbox_exe: Option, cli_config_overrides: CliConfigOverrides, loader_overrides: LoaderOverrides, + default_analytics_enabled: bool, ) -> IoResult<()> { // Set up channels. let (incoming_tx, mut incoming_rx) = mpsc::channel::(CHANNEL_CAPACITY); @@ -96,7 +97,7 @@ pub async fn run_main( &config, env!("CARGO_PKG_VERSION"), Some("codex_app_server"), - false, + default_analytics_enabled, ) .map_err(|e| { std::io::Error::new( diff --git a/codex-rs/app-server/src/main.rs b/codex-rs/app-server/src/main.rs index be57311e8..71d6dc338 100644 --- a/codex-rs/app-server/src/main.rs +++ b/codex-rs/app-server/src/main.rs @@ -20,6 +20,7 @@ fn main() -> anyhow::Result<()> { codex_linux_sandbox_exe, CliConfigOverrides::default(), loader_overrides, + false, ) .await?; Ok(()) diff --git a/codex-rs/app-server/tests/suite/v2/analytics.rs b/codex-rs/app-server/tests/suite/v2/analytics.rs new file mode 100644 index 000000000..e18a0d3c8 --- /dev/null +++ b/codex-rs/app-server/tests/suite/v2/analytics.rs @@ -0,0 +1,66 @@ +use anyhow::Result; +use codex_core::config::ConfigBuilder; +use codex_core::config::types::OtelExporterKind; +use codex_core::config::types::OtelHttpProtocol; +use pretty_assertions::assert_eq; +use std::collections::HashMap; +use tempfile::TempDir; + +const SERVICE_VERSION: &str = "0.0.0-test"; + +fn set_metrics_exporter(config: &mut codex_core::config::Config) { + config.otel.metrics_exporter = OtelExporterKind::OtlpHttp { + endpoint: "http://localhost:4318".to_string(), + headers: HashMap::new(), + protocol: OtelHttpProtocol::Json, + tls: None, + }; +} + +#[tokio::test] +async fn app_server_default_analytics_disabled_without_flag() -> Result<()> { + let codex_home = TempDir::new()?; + let mut config = ConfigBuilder::default() + .codex_home(codex_home.path().to_path_buf()) + .build() + .await?; + set_metrics_exporter(&mut config); + config.analytics_enabled = None; + + let provider = codex_core::otel_init::build_provider( + &config, + SERVICE_VERSION, + Some("codex_app_server"), + false, + ) + .map_err(|err| anyhow::anyhow!(err.to_string()))?; + + // With analytics unset in the config and the default flag is false, metrics are disabled. + // No provider is built. + assert_eq!(provider.is_none(), true); + Ok(()) +} + +#[tokio::test] +async fn app_server_default_analytics_enabled_with_flag() -> Result<()> { + let codex_home = TempDir::new()?; + let mut config = ConfigBuilder::default() + .codex_home(codex_home.path().to_path_buf()) + .build() + .await?; + set_metrics_exporter(&mut config); + config.analytics_enabled = None; + + let provider = codex_core::otel_init::build_provider( + &config, + SERVICE_VERSION, + Some("codex_app_server"), + true, + ) + .map_err(|err| anyhow::anyhow!(err.to_string()))?; + + // With analytics unset in the config and the default flag is true, metrics are enabled. + let has_metrics = provider.as_ref().and_then(|otel| otel.metrics()).is_some(); + assert_eq!(has_metrics, true); + Ok(()) +} diff --git a/codex-rs/app-server/tests/suite/v2/mod.rs b/codex-rs/app-server/tests/suite/v2/mod.rs index 5c40c5fc1..b2159ab9c 100644 --- a/codex-rs/app-server/tests/suite/v2/mod.rs +++ b/codex-rs/app-server/tests/suite/v2/mod.rs @@ -1,4 +1,5 @@ mod account; +mod analytics; mod config_rpc; mod initialize; mod model_list; diff --git a/codex-rs/cli/src/main.rs b/codex-rs/cli/src/main.rs index 918626c71..c29da7271 100644 --- a/codex-rs/cli/src/main.rs +++ b/codex-rs/cli/src/main.rs @@ -268,6 +268,24 @@ struct AppServerCommand { /// Omit to run the app server; specify a subcommand for tooling. #[command(subcommand)] subcommand: Option, + + /// Controls whether analytics are enabled by default. + /// + /// Analytics are disabled by default for app-server. Users have to explicitly opt in + /// via the `analytics` section in the config.toml file. + /// + /// However, for first-party use cases like the VSCode IDE extension, we default analytics + /// to be enabled by default by setting this flag. Users can still opt out by setting this + /// in their config.toml: + /// + /// ```toml + /// [analytics] + /// enabled = false + /// ``` + /// + /// See https://developers.openai.com/codex/config-advanced/#metrics for more details. + #[arg(long = "analytics-default-enabled")] + analytics_default_enabled: bool, } #[derive(Debug, clap::Subcommand)] @@ -500,6 +518,7 @@ async fn cli_main(codex_linux_sandbox_exe: Option) -> anyhow::Result<() codex_linux_sandbox_exe, root_config_overrides, codex_core::config_loader::LoaderOverrides::default(), + app_server_cli.analytics_default_enabled, ) .await?; } @@ -910,6 +929,14 @@ mod tests { finalize_fork_interactive(interactive, root_overrides, session_id, last, all, fork_cli) } + fn app_server_from_args(args: &[&str]) -> AppServerCommand { + let cli = MultitoolCli::try_parse_from(args).expect("parse"); + let Subcommand::AppServer(app_server) = cli.subcommand.expect("app-server present") else { + unreachable!() + }; + app_server + } + fn sample_exit_info(conversation: Option<&str>) -> AppExitInfo { let token_usage = TokenUsage { output_tokens: 2, @@ -1108,6 +1135,19 @@ mod tests { assert!(interactive.fork_show_all); } + #[test] + fn app_server_analytics_default_disabled_without_flag() { + let app_server = app_server_from_args(["codex", "app-server"].as_ref()); + assert!(!app_server.analytics_default_enabled); + } + + #[test] + fn app_server_analytics_default_enabled_with_flag() { + let app_server = + app_server_from_args(["codex", "app-server", "--analytics-default-enabled"].as_ref()); + assert!(app_server.analytics_default_enabled); + } + #[test] fn feature_toggles_known_features_generate_overrides() { let toggles = FeatureToggles {