From a5c409243744c3b99776259eea2f3a13d6af4447 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Mon, 2 Nov 2026 18:15:35 +0000 Subject: [PATCH] Retire the daemon_auto_start feature flag (#58940) ## What changed Eligible interactive launches now start or reuse the shared daemon regardless of `features.daemon_auto_start`. Remote-control daemon eligibility also no longer checks this flag. Use `--no-daemon` to run without the shared background server. Treat `daemon_auto_start` as removed: ignore configured values with a migration notice, ignore managed requirements with a warning, and hide the flag from experimental feature discovery. Remove its launch telemetry tag. ## Testing Extend retired-feature requirement coverage and add a migration-notice snapshot. Update CLI and TUI integration tests for daemon startup and explicit `--no-daemon` usage. GitOrigin-RevId: e0290e5acd4bc0169a0f71c3296c567e46269ce3 --- codex-rs/cli/src/remote_control_cmd.rs | 4 +- codex-rs/cli/tests/daemon_startup.rs | 9 +-- codex-rs/cli/tests/worktree.rs | 13 +---- codex-rs/core/src/config/config_tests.rs | 1 + codex-rs/core/src/config/managed_features.rs | 5 +- codex-rs/features/src/lib.rs | 15 ++++- .../bottom_pane/experimental_features_view.rs | 5 +- .../experimental_features_view_tests.rs | 1 + codex-rs/tui/src/experimental_features.rs | 3 +- .../tui/src/experimental_features_tests.rs | 3 - ...s__daemon_auto_start_migration_notice.snap | 8 +++ codex-rs/tui/src/history_cell/tests.rs | 29 +++++++--- codex-rs/tui/src/startup_orchestration.rs | 18 +----- .../tui/tests/suite/daemon_compatibility.rs | 58 ++++++++++--------- codex-rs/tui/tests/suite/directory_trust.rs | 4 +- codex-rs/tui/tests/suite/focus_palette.rs | 20 ++----- codex-rs/tui/tests/suite/reconnect.rs | 9 ++- codex-rs/tui/tests/suite/screen_reader.rs | 2 +- .../tui/tests/suite/tui_mode_picker_tests.rs | 2 +- codex-rs/tui/tests/suite/worktree_stack.rs | 1 + 20 files changed, 111 insertions(+), 99 deletions(-) create mode 100644 codex-rs/tui/src/history_cell/snapshots/codex_tui__history_cell__tests__daemon_auto_start_migration_notice.snap diff --git a/codex-rs/cli/src/remote_control_cmd.rs b/codex-rs/cli/src/remote_control_cmd.rs index 21cff3a7a6..d5c5a721bb 100644 --- a/codex-rs/cli/src/remote_control_cmd.rs +++ b/codex-rs/cli/src/remote_control_cmd.rs @@ -16,7 +16,6 @@ use codex_app_server_protocol::RemoteControlPairingStartResponse; use codex_arg0::Arg0DispatchPaths; use codex_config::LoaderOverrides; use codex_core::config::ConfigBuilder; -use codex_features::Feature; use codex_protocol::protocol::SessionSource; use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_cli::CliConfigOverrides; @@ -161,8 +160,7 @@ async fn daemon_eligible(root_config_overrides: &CliConfigOverrides) -> anyhow:: let Ok(config) = ConfigBuilder::default().build().await else { return Ok(false); }; - Ok(config.features.enabled(Feature::DaemonAutoStart) - && !codex_tui::uses_wsl_drvfs(&config.codex_home)) + Ok(!codex_tui::uses_wsl_drvfs(&config.codex_home)) } fn print_remote_control_progress(json: bool, message: &str) -> anyhow::Result<()> { diff --git a/codex-rs/cli/tests/daemon_startup.rs b/codex-rs/cli/tests/daemon_startup.rs index 319a43eb4a..87b0cf590d 100644 --- a/codex-rs/cli/tests/daemon_startup.rs +++ b/codex-rs/cli/tests/daemon_startup.rs @@ -40,7 +40,7 @@ async fn agents_checks_trust_when_starting_after_dismissing_overview() -> Result #[tokio::test] #[cfg(unix)] -async fn discovered_managed_daemon_can_restart_with_auto_start_disabled() -> Result<()> { +async fn discovered_managed_daemon_can_restart() -> Result<()> { daemon_startup("discovered").await } @@ -194,8 +194,10 @@ async fn daemon_startup(command: &str) -> Result<()> { {}\n\ notice.model_migrations.\"gpt-5.6-terra\" = \"gpt-6-sol\"\n\ [projects.{}]\ntrust_level = \"{}\"\n", - if command == "discovered" { - "tui.status_line=[\"model\",\"thread-id\",\"thread-name\"]\nfeatures.daemon_auto_start=false" + if command == "start" { + "features.daemon_auto_start=false" + } else if command == "discovered" { + "tui.status_line=[\"model\",\"thread-id\",\"thread-name\"]" } else if bedrock_onboarding { "features.gcp_setup_wizard=false" } else { @@ -321,7 +323,6 @@ async fn daemon_startup(command: &str) -> Result<()> { } "entercontinue·escback" } else if command == "discovered" { - args.extend(["--disable".into(), "daemon_auto_start".into()]); // Empty drafts need not survive a daemon restart. Renaming materializes the // thread; wait for the confirmed footer name, not the echoed command. steps.push_back(("GPT-5.6-Terra", b"/rename daemon-restart-test\r")); diff --git a/codex-rs/cli/tests/worktree.rs b/codex-rs/cli/tests/worktree.rs index 13b6914968..dd542f9b99 100644 --- a/codex-rs/cli/tests/worktree.rs +++ b/codex-rs/cli/tests/worktree.rs @@ -192,7 +192,6 @@ cli_auth_credentials_store = "file" chatgpt_base_url = "{}/source/backend-api" analytics.enabled = false check_for_update_on_startup = false -features.daemon_auto_start = {} features.mcp_oauth_refresh_coordination = true model_provider = "local" model = "test-model" @@ -211,7 +210,6 @@ trust_level = "trusted" trust_level = "trusted" "#, server.uri(), - backend == "daemon", server.uri(), serde_json::to_string(&source)?, serde_json::to_string(&launcher)? @@ -390,6 +388,9 @@ trust_level = "trusted" .mount(&server) .await; let mut args = worktree_args.clone(); + if backend == "embedded" && !analytics { + args.push("--no-daemon".into()); + } if backend == "daemon" && previous.is_empty() { args.extend(["-c".into(), "features.auth_elicitation=true".into()]); } @@ -698,14 +699,6 @@ trust_level = "trusted" ("terminal_rows", "40-79"), ("multiplexer", "none"), ("daemon_selection_reason", "incompatible_option"), - ( - "daemon_auto_start", - if backend == "daemon" { - "enabled" - } else { - "disabled" - }, - ), ("auto_update", "enabled"), ("auto_update_setting", "default"), ("update_interval_setting", "default"), diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index 8b2548f7ea..714a0da53b 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -12363,6 +12363,7 @@ shell_tool = false #[test_case::test_case(Feature::Personality; "personality")] #[test_case::test_case(Feature::GuardianThreadContext; "guardian thread context")] +#[test_case::test_case(Feature::DaemonAutoStart; "daemon auto start")] #[test_case::test_case(Feature::Worktrees; "worktrees")] #[test_case::test_case(Feature::RealtimeConversation; "realtime conversation")] #[test_case::test_case(Feature::ShellZshFork; "shell zsh fork")] diff --git a/codex-rs/core/src/config/managed_features.rs b/codex-rs/core/src/config/managed_features.rs index 2fd34d2f0c..782adc52c4 100644 --- a/codex-rs/core/src/config/managed_features.rs +++ b/codex-rs/core/src/config/managed_features.rs @@ -244,7 +244,10 @@ fn parse_feature_requirements( if key == Feature::Personality.key() { continue; } - if key == Feature::Worktrees.key() || key == Feature::RealtimeConversation.key() { + if matches!( + key.as_str(), + "daemon_auto_start" | "worktrees" | "realtime_conversation" + ) { push_feature_requirement_warning( &mut startup_warnings, format!( diff --git a/codex-rs/features/src/lib.rs b/codex-rs/features/src/lib.rs index 38962d5934..1cfd18416c 100644 --- a/codex-rs/features/src/lib.rs +++ b/codex-rs/features/src/lib.rs @@ -121,7 +121,7 @@ pub enum Feature { /// Store CLI auth in the encrypted local secrets backend when keyring storage is selected. SecretAuthStorage, - /// Automatically start the shared local daemon for eligible interactive launches. + /// Removed compatibility flag. Eligible interactive launches always start or reuse the daemon. DaemonAutoStart, /// Allow the TUI to discover and select among multiple conversation backends. TuiMultiBackend, @@ -720,6 +720,13 @@ impl Features { ); continue; } + "daemon_auto_start" => { + self.record_legacy_usage_force( + "features.daemon_auto_start", + Feature::DaemonAutoStart, + ); + continue; + } "transcript_v2" => { self.record_legacy_usage_force("features.transcript_v2", Feature::TranscriptV2); continue; @@ -843,6 +850,10 @@ fn legacy_usage_notice(alias: &str, feature: Feature) -> (String, Option "`[features.guardianv2].thread_context` is deprecated and ignored.".to_string(), Some("Thread-owned Guardian context is always enabled. Remove `thread_context` from [features.guardianv2] in config.toml, including profile overrides.".to_string()), ), + Feature::DaemonAutoStart => ( + "`[features].daemon_auto_start` is deprecated and ignored.".to_string(), + Some("Remove `daemon_auto_start` from [features] in config.toml or profile configuration files. Use `--no-daemon` to run without the shared background server.".to_string()), + ), Feature::TranscriptV2 => ( "`[features].transcript_v2` is deprecated and ignored.".to_string(), Some("Use `[tui].fullscreen_transcript` in config.toml instead.".to_string()), @@ -1107,7 +1118,7 @@ pub const FEATURES: &[FeatureSpec] = &[ FeatureSpec { id: Feature::DaemonAutoStart, key: "daemon_auto_start", - stage: Stage::Stable, + stage: Stage::Removed, default_enabled: true, }, FeatureSpec { diff --git a/codex-rs/tui/src/bottom_pane/experimental_features_view.rs b/codex-rs/tui/src/bottom_pane/experimental_features_view.rs index 681698d984..8a48eb9031 100644 --- a/codex-rs/tui/src/bottom_pane/experimental_features_view.rs +++ b/codex-rs/tui/src/bottom_pane/experimental_features_view.rs @@ -282,7 +282,10 @@ impl BottomPaneView for ExperimentalFeaturesView { let mut count = 0; for feature in features { if feature.stage != ExperimentalFeatureStage::Beta - || matches!(feature.name.as_str(), "worktrees" | "realtime_conversation") + || matches!( + feature.name.as_str(), + "daemon_auto_start" | "worktrees" | "realtime_conversation" + ) { continue; } diff --git a/codex-rs/tui/src/bottom_pane/experimental_features_view_tests.rs b/codex-rs/tui/src/bottom_pane/experimental_features_view_tests.rs index c3a0ff6a92..3e8122fef2 100644 --- a/codex-rs/tui/src/bottom_pane/experimental_features_view_tests.rs +++ b/codex-rs/tui/src/bottom_pane/experimental_features_view_tests.rs @@ -104,6 +104,7 @@ fn experimental_features_discovery_preserves_server_order_and_accepts_new_keys() .send(Ok(vec![ server_feature("network_proxy"), server_feature("future_feature"), + server_feature("daemon_auto_start"), server_feature("worktrees"), server_feature("realtime_conversation"), stable, diff --git a/codex-rs/tui/src/experimental_features.rs b/codex-rs/tui/src/experimental_features.rs index 270a24c4f8..bf4b7bd895 100644 --- a/codex-rs/tui/src/experimental_features.rs +++ b/codex-rs/tui/src/experimental_features.rs @@ -109,8 +109,7 @@ pub(crate) async fn write( let key = format!("features.{}", serde_json::json!(name)); Ok(crate::config_update::replace_config_value( key, - // Keep the daemon opt-out explicit even before rollout defaults enable it. - if *enabled || feature.default_enabled || name == "daemon_auto_start" { + if *enabled || feature.default_enabled { serde_json::json!(enabled) } else { serde_json::Value::Null diff --git a/codex-rs/tui/src/experimental_features_tests.rs b/codex-rs/tui/src/experimental_features_tests.rs index 1e41076579..a8a64fa610 100644 --- a/codex-rs/tui/src/experimental_features_tests.rs +++ b/codex-rs/tui/src/experimental_features_tests.rs @@ -181,7 +181,6 @@ async fn experimental_feature_writes_use_server_defaults_and_refresh_configured_ "initialize" => json!({"userAgent": "experimental-test"}), "experimentalFeature/list" => { json!({"data": [ - {"name": "daemon_auto_start", "stage": "beta", "displayName": null, "description": null, "announcement": null, "enabled": !wrote || scenario == "overridden", "defaultEnabled": false}, {"name": "default_off", "stage": "beta", "displayName": null, "description": null, "announcement": null, "enabled": !wrote || scenario == "overridden", "defaultEnabled": false}, {"name": "default_on", "stage": "beta", "displayName": null, "description": null, "announcement": null, "enabled": !wrote, "defaultEnabled": true}, {"name": "new_server_feature", "stage": "beta", "displayName": null, "description": null, "announcement": null, "enabled": wrote, "defaultEnabled": false} @@ -192,7 +191,6 @@ async fn experimental_feature_writes_use_server_defaults_and_refresh_configured_ request.params, Some(json!({ "edits": [ - {"keyPath": "features.\"daemon_auto_start\"", "value": false, "mergeStrategy": "replace"}, {"keyPath": "features.\"default_off\"", "value": null, "mergeStrategy": "replace"}, {"keyPath": "features.\"default_on\"", "value": false, "mergeStrategy": "replace"}, {"keyPath": "features.\"new_server_feature\"", "value": true, "mergeStrategy": "replace"} @@ -229,7 +227,6 @@ async fn experimental_feature_writes_use_server_defaults_and_refresh_configured_ handle, thread_id, vec![ - ("daemon_auto_start".to_string(), false), ("default_off".to_string(), false), ("default_on".to_string(), false), ("new_server_feature".to_string(), true), diff --git a/codex-rs/tui/src/history_cell/snapshots/codex_tui__history_cell__tests__daemon_auto_start_migration_notice.snap b/codex-rs/tui/src/history_cell/snapshots/codex_tui__history_cell__tests__daemon_auto_start_migration_notice.snap new file mode 100644 index 0000000000..04ba91cf33 --- /dev/null +++ b/codex-rs/tui/src/history_cell/snapshots/codex_tui__history_cell__tests__daemon_auto_start_migration_notice.snap @@ -0,0 +1,8 @@ +--- +source: tui/src/history_cell/tests.rs +expression: "render_lines(&cell.transcript_lines(80)).join(\"\\n\")" +--- +⚠ `[features].daemon_auto_start` is deprecated and ignored. +Remove `daemon_auto_start` from [features] in config.toml or profile +configuration files. Use `--no-daemon` to run without the shared background +server. diff --git a/codex-rs/tui/src/history_cell/tests.rs b/codex-rs/tui/src/history_cell/tests.rs index d0e70b0574..8c12d0e88e 100644 --- a/codex-rs/tui/src/history_cell/tests.rs +++ b/codex-rs/tui/src/history_cell/tests.rs @@ -2973,15 +2973,26 @@ fn deprecation_notice_renders_summary_with_details() { } #[test] -fn legacy_zsh_migration_notice_snapshot() { - let mut features = codex_features::Features::default(); - features.record_legacy_usage_force("shell_zsh_fork", codex_features::Feature::ShellZshFork); - let usage = features - .legacy_feature_usages() - .next() - .expect("migration notice"); - let cell = new_deprecation_notice(usage.summary.clone(), usage.details.clone()); - insta::assert_snapshot!(render_lines(&cell.transcript_lines(/*width*/ 80)).join("\n")); +fn legacy_feature_migration_notices() { + for (key, snapshot) in [ + ("shell_zsh_fork", "legacy_zsh_migration_notice_snapshot"), + ("daemon_auto_start", "daemon_auto_start_migration_notice"), + ] { + let mut features = codex_features::Features::with_defaults(); + features.apply_map(&std::collections::BTreeMap::from([( + key.to_string(), + false, + )])); + let usage = features + .legacy_feature_usages() + .next() + .expect("migration notice"); + let cell = new_deprecation_notice(usage.summary.clone(), usage.details.clone()); + insta::assert_snapshot!( + snapshot, + render_lines(&cell.transcript_lines(/*width*/ 80)).join("\n") + ); + } } #[test] diff --git a/codex-rs/tui/src/startup_orchestration.rs b/codex-rs/tui/src/startup_orchestration.rs index ac7e596488..4dc9f5d417 100644 --- a/codex-rs/tui/src/startup_orchestration.rs +++ b/codex-rs/tui/src/startup_orchestration.rs @@ -568,7 +568,6 @@ pub(super) async fn run_main_inner( None }; let auto_start_daemon = cfg!(any(unix, windows)) - && (cli.agents_overview || config.features.enabled(Feature::DaemonAutoStart)) && implicit_connection && !cli.no_daemon && !app_server_target.uses_remote_workspace(); @@ -736,7 +735,7 @@ pub(super) async fn run_main_inner( (_, Some(_)) => "incompatible_option", _ if auto_start_daemon => "auto_start", (AppServerTarget::LocalDaemon { .. }, _) => "existing_daemon", - (AppServerTarget::Embedded, None) => "auto_start_disabled", + (AppServerTarget::Embedded, None) => "unclassified", }; let daemon_settings = if metrics.is_some() { codex_app_server_daemon::telemetry::settings_tags(&config.codex_home) @@ -747,17 +746,7 @@ pub(super) async fn run_main_inner( }; let mut launch_tags = daemon_settings.to_vec(); launch_tags.extend(terminal_size_tags); - launch_tags.extend([ - ("daemon_selection_reason", selection_reason), - ( - "daemon_auto_start", - if config.features.enabled(Feature::DaemonAutoStart) { - "enabled" - } else { - "disabled" - }, - ), - ]); + launch_tags.push(("daemon_selection_reason", selection_reason)); let selected_local_daemon = matches!(app_server_target, AppServerTarget::LocalDaemon { .. }); let launch_telemetry = move |app_server_mode| { let Some(metrics) = metrics else { @@ -817,9 +806,6 @@ pub(super) async fn run_main_inner( Some(daemon_startup::WSL_DRVFS_EXCLUSION) => "wsl_drvfs", Some("this Windows launcher") => "windows_detach_restricted", Some("daemon feature settings") => "daemon_compatibility", - None if selection_reason == "auto_start_disabled" => { - "auto_start_disabled_no_daemon" - } None if selected_local_daemon => "daemon_connection_failed", None if selection_reason == "agents" => "agents_no_daemon_discovered", Some(_) | None => "unclassified", diff --git a/codex-rs/tui/tests/suite/daemon_compatibility.rs b/codex-rs/tui/tests/suite/daemon_compatibility.rs index 86e8b77910..d135574836 100644 --- a/codex-rs/tui/tests/suite/daemon_compatibility.rs +++ b/codex-rs/tui/tests/suite/daemon_compatibility.rs @@ -21,37 +21,43 @@ async fn incompatible_daemon_can_run_embedded_for_explicit_features() -> Result< let socket = codex_app_server_client::app_server_control_socket_path(home.path())?; std::fs::create_dir_all(socket.parent().unwrap())?; let listener = UnixListener::bind(socket.as_path())?; + let server_home = home.path().to_path_buf(); let server = tokio::spawn(async move { - let mut socket = loop { - let (stream, _) = listener.accept().await?; - if let Ok(socket) = tokio_tungstenite::accept_async(stream).await { - break socket; - } - }; - while let Some(Ok(Message::Text(text))) = socket.next().await { - let JSONRPCMessage::Request(request) = serde_json::from_str(&text)? else { - continue; + // Startup probes the running daemon, then checks its feature settings. + for _ in 0..2 { + let mut socket = loop { + let (stream, _) = listener.accept().await?; + if let Ok(socket) = tokio_tungstenite::accept_async(stream).await { + break socket; + } }; - let response = match request.method.as_str() { - "initialize" => { - json!({"id": request.id, "result": {"userAgent": "daemon-test/0.0.0"}}) - } - "config/read" => { - json!({"id": request.id, "result": {"config": {}, "layers": []}}) - } - "configRequirements/read" => { - json!({"id": request.id, "result": {"requirements": null}}) - } - "experimentalFeature/list" => json!({"id": request.id, "result": {"data": [{ + while let Some(Ok(Message::Text(text))) = socket.next().await { + let JSONRPCMessage::Request(request) = serde_json::from_str(&text)? else { + continue; + }; + let response = match request.method.as_str() { + "initialize" => { + json!({"id": request.id, "result": {"userAgent": "daemon-test/0.0.0", "codexHome": server_home, "platformFamily": std::env::consts::FAMILY, "platformOs": std::env::consts::OS}}) + } + "config/read" => { + json!({"id": request.id, "result": {"config": {}, "layers": []}}) + } + "configRequirements/read" => { + json!({"id": request.id, "result": {"requirements": null}}) + } + "experimentalFeature/list" => { + json!({"id": request.id, "result": {"data": [{ "name": "api_key_model_discovery", "stage": "stable", "displayName": null, "description": null, "announcement": null, "enabled": scenario == "explicit disable", "defaultEnabled": true, - }], "nextCursor": null}}), - _ => panic!("unexpected request: {}", request.method), - }; - socket - .send(Message::Text(response.to_string().into())) - .await?; + }], "nextCursor": null}}) + } + _ => panic!("unexpected request: {}", request.method), + }; + socket + .send(Message::Text(response.to_string().into())) + .await?; + } } Ok::<_, anyhow::Error>(()) }); diff --git a/codex-rs/tui/tests/suite/directory_trust.rs b/codex-rs/tui/tests/suite/directory_trust.rs index 0cfbfc252a..2539c6e08e 100644 --- a/codex-rs/tui/tests/suite/directory_trust.rs +++ b/codex-rs/tui/tests/suite/directory_trust.rs @@ -39,6 +39,7 @@ async fn connected_trust_cancellation_and_acceptance_control_task_creation() -> let cwd = repo_root.clone(); let methods = Arc::new(Mutex::new(Vec::new())); let requests = Arc::clone(&methods); + let server_home = codex_home.path().to_path_buf(); let server = tokio::spawn(async move { let mut clients = tokio::task::JoinSet::new(); let trust_reads = Arc::new(AtomicUsize::new(0)); @@ -48,6 +49,7 @@ async fn connected_trust_cancellation_and_acceptance_control_task_creation() -> .await .expect("accept fake app-server client"); let cwd = cwd.clone(); + let server_home = server_home.clone(); let methods = Arc::clone(&requests); let trust_reads = Arc::clone(&trust_reads); clients.spawn(async move { @@ -74,7 +76,7 @@ async fn connected_trust_cancellation_and_acceptance_control_task_creation() -> }; methods.lock().unwrap().push(request.method.clone()); let result = match request.method.as_str() { - "initialize" => json!({"userAgent": "trust-pty"}), + "initialize" => json!({"userAgent": "trust-pty/0.0.0", "codexHome": server_home, "platformFamily": std::env::consts::FAMILY, "platformOs": std::env::consts::OS}), "experimentalFeature/list" => json!({"data": (["api_key_model_discovery", "code_mode_host", "auth_elicitation"].map(|name| json!({ "name": name, "stage": "stable", "displayName": null, "description": null, "announcement": null, diff --git a/codex-rs/tui/tests/suite/focus_palette.rs b/codex-rs/tui/tests/suite/focus_palette.rs index 084599ce1c..58f32a5154 100644 --- a/codex-rs/tui/tests/suite/focus_palette.rs +++ b/codex-rs/tui/tests/suite/focus_palette.rs @@ -35,7 +35,8 @@ fn focus_gained_with_unanswered_palette_queries_preserves_immediate_input() -> R let codex_home = tempfile::tempdir()?; write_test_config(codex_home.path(), &repo_root)?; - let mut terminal = PtyCodex::start(&repo_root, codex_home, &["--no-alt-screen"])?; + let mut terminal = + PtyCodex::start(&repo_root, codex_home, &["--no-daemon", "--no-alt-screen"])?; terminal.wait_for_startup()?; let startup_output_len = terminal.output.len(); @@ -165,7 +166,7 @@ fn default_owned_screen_entry_paints_before_sync_ends_and_exit_clears_inline_dra let repo_root = codex_utils_cargo_bin::repo_root()?; let codex_home = tempfile::tempdir()?; write_test_config(codex_home.path(), &repo_root)?; - let mut terminal = PtyCodex::start(&repo_root, codex_home, &[])?; + let mut terminal = PtyCodex::start(&repo_root, codex_home, &["--no-daemon"])?; terminal.wait_for_startup()?; let deadline = Instant::now() + STARTUP_TIMEOUT; while !terminal.parser.screen().alternate_screen() && Instant::now() < deadline { @@ -280,7 +281,7 @@ fn fullscreen_transcript_can_opt_out_to_terminal_scrollback() -> Result<()> { let mut terminal = PtyCodex::start( &repo_root, codex_home, - &["-c", "tui.fullscreen_transcript=false"], + &["--no-daemon", "-c", "tui.fullscreen_transcript=false"], )?; terminal.wait_for_startup()?; terminal.wait_for_screen("GPT-5.6-Terra")?; @@ -570,7 +571,6 @@ pub(super) fn write_test_config(codex_home: &Path, repo_root: &Path) -> Result<( let config = format!( "model = \"gpt-5.6-terra\"\nmodel_provider = \"openai\"\n\ suppress_unstable_features_warning = true\nanalytics.enabled = false\n\ - features.daemon_auto_start = false\n\ notice.model_migrations.\"gpt-5.6-terra\" = \"gpt-6-sol\"\n\n\ [projects.\"{repo_root}\"]\ntrust_level = \"trusted\"\n" ); @@ -589,12 +589,6 @@ fn no_daemon_skips_startup_and_discovery() -> Result<()> { let workspace = tempfile::tempdir()?; let home = tempfile::tempdir()?; write_test_config(home.path(), workspace.path())?; - let config = home.path().join("config.toml"); - let contents = std::fs::read_to_string(&config)?; - std::fs::write( - config, - contents.replace("features.daemon_auto_start = false\n", ""), - )?; let socket_path = codex_app_server_client::app_server_control_socket_path(home.path())?; std::fs::create_dir_all(socket_path.as_path().parent().unwrap())?; let listener = if running { @@ -637,12 +631,6 @@ fn auto_daemon_start_failure_exits_with_manual_fallback_hint() -> Result<()> { let workspace = tempfile::tempdir()?; let home = tempfile::tempdir()?; write_test_config(home.path(), workspace.path())?; - let config = home.path().join("config.toml"); - let contents = std::fs::read_to_string(&config)?; - std::fs::write( - config, - contents.replace("features.daemon_auto_start = false\n", ""), - )?; // An incomplete selected package must fail without installing a replacement. std::fs::create_dir_all(home.path().join("packages/app-server-daemon/current"))?; let mut terminal = PtyCodex::start( diff --git a/codex-rs/tui/tests/suite/reconnect.rs b/codex-rs/tui/tests/suite/reconnect.rs index 1677ba7b81..a20cb39d20 100644 --- a/codex-rs/tui/tests/suite/reconnect.rs +++ b/codex-rs/tui/tests/suite/reconnect.rs @@ -41,6 +41,7 @@ async fn automatic_reconnect_restores_draft_and_routes_new_notifications() -> Re let (submitted_tx, submitted_rx) = tokio::sync::oneshot::channel(); let server_cwd = workspace_path.clone(); let id = "00000000-0000-0000-0000-000000000001"; + let server_home = codex_home.path().to_path_buf(); let server = tokio::spawn(async move { let mut methods = Vec::new(); let mut restore_rx = Some(restore_rx); @@ -52,8 +53,8 @@ async fn automatic_reconnect_restores_draft_and_routes_new_notifications() -> Re "status": {"type": "active", "activeFlags": []}, "cwd": server_cwd, "cliVersion": "0.0.0", "source": "cli", "turns": [{"id": "running", "items": [], "status": "inProgress", "error": null}] }); - // The first connection checks daemon compatibility before TUI startup. - for connection in -1..7 { + // Startup probes the running daemon and checks compatibility before opening the session. + for connection in -2..7 { let mut socket = loop { let (stream, _) = listener.accept().await?; // Startup probes the default daemon socket before opening its WebSocket. @@ -105,7 +106,9 @@ async fn automatic_reconnect_restores_draft_and_routes_new_notifications() -> Re continue; } let result = match request.method.as_str() { - "initialize" => json!({"userAgent": "reconnect-pty"}), + "initialize" => { + json!({"userAgent": "reconnect-pty/0.0.0", "codexHome": server_home, "platformFamily": std::env::consts::FAMILY, "platformOs": std::env::consts::OS}) + } // An older daemon can omit the client's default-disabled features. "experimentalFeature/list" => { json!({"data": (["api_key_model_discovery", "code_mode_host", "auth_elicitation"].map(|name| json!({ diff --git a/codex-rs/tui/tests/suite/screen_reader.rs b/codex-rs/tui/tests/suite/screen_reader.rs index 5c6195d499..7207e6174e 100644 --- a/codex-rs/tui/tests/suite/screen_reader.rs +++ b/codex-rs/tui/tests/suite/screen_reader.rs @@ -15,7 +15,7 @@ fn startup_records_screen_reader_detection() -> Result<()> { let path = home.path().join("config.toml"); let contents = std::fs::read_to_string(&path)?; std::fs::write(&path, format!("{contents}\n[tui]\nanimations = true\n"))?; - let mut terminal = PtyCodex::start(&repo_root, home, &[])?; + let mut terminal = PtyCodex::start(&repo_root, home, &["--no-daemon"])?; terminal.wait_for_startup()?; let deadline = Instant::now() + Duration::from_secs(/*secs*/ 30); loop { diff --git a/codex-rs/tui/tests/suite/tui_mode_picker_tests.rs b/codex-rs/tui/tests/suite/tui_mode_picker_tests.rs index 88945a613d..604f4fd4db 100644 --- a/codex-rs/tui/tests/suite/tui_mode_picker_tests.rs +++ b/codex-rs/tui/tests/suite/tui_mode_picker_tests.rs @@ -17,7 +17,7 @@ fn tui_mode_picker_applies_only_after_restart() -> Result<()> { ), )?; for (launch, current) in [false, true, false].into_iter().enumerate() { - let mut terminal = PtyCodex::start(&root, home, &[])?; + let mut terminal = PtyCodex::start(&root, home, &["--no-daemon"])?; terminal.wait_for_startup()?; // The header precedes session setup; wait for the thread ID before sending commands. let deadline = Instant::now() + STARTUP_TIMEOUT; diff --git a/codex-rs/tui/tests/suite/worktree_stack.rs b/codex-rs/tui/tests/suite/worktree_stack.rs index ecec4a8955..6c7560f932 100644 --- a/codex-rs/tui/tests/suite/worktree_stack.rs +++ b/codex-rs/tui/tests/suite/worktree_stack.rs @@ -99,6 +99,7 @@ async fn picker_side_worktree_fork_and_cd_run_on_the_production_stack() -> Resul &root, codex_home, &[ + "--no-daemon", "--no-alt-screen", "-c", "features.worktrees=true",