diff --git a/codex-rs/cli/src/remote_control_cmd.rs b/codex-rs/cli/src/remote_control_cmd.rs index d5c5a721bb..21cff3a7a6 100644 --- a/codex-rs/cli/src/remote_control_cmd.rs +++ b/codex-rs/cli/src/remote_control_cmd.rs @@ -16,6 +16,7 @@ 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; @@ -160,7 +161,8 @@ async fn daemon_eligible(root_config_overrides: &CliConfigOverrides) -> anyhow:: let Ok(config) = ConfigBuilder::default().build().await else { return Ok(false); }; - Ok(!codex_tui::uses_wsl_drvfs(&config.codex_home)) + Ok(config.features.enabled(Feature::DaemonAutoStart) + && !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 17d4f1191b..42e9742a6c 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() -> Result<()> { +async fn discovered_managed_daemon_can_restart_with_auto_start_disabled() -> Result<()> { daemon_startup("discovered").await } @@ -194,10 +194,8 @@ 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 == "start" { - "features.daemon_auto_start=false" - } else if command == "discovered" { - "tui.status_line=[\"model\",\"thread-id\",\"thread-name\"]" + if command == "discovered" { + "tui.status_line=[\"model\",\"thread-id\",\"thread-name\"]\nfeatures.daemon_auto_start=false" } else if bedrock_onboarding { "features.gcp_setup_wizard=false" } else { @@ -332,6 +330,7 @@ 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 dd542f9b99..13b6914968 100644 --- a/codex-rs/cli/tests/worktree.rs +++ b/codex-rs/cli/tests/worktree.rs @@ -192,6 +192,7 @@ 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" @@ -210,6 +211,7 @@ trust_level = "trusted" trust_level = "trusted" "#, server.uri(), + backend == "daemon", server.uri(), serde_json::to_string(&source)?, serde_json::to_string(&launcher)? @@ -388,9 +390,6 @@ 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()]); } @@ -699,6 +698,14 @@ 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 a3adab02bc..c34c3d564a 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -12446,7 +12446,6 @@ 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 782adc52c4..2fd34d2f0c 100644 --- a/codex-rs/core/src/config/managed_features.rs +++ b/codex-rs/core/src/config/managed_features.rs @@ -244,10 +244,7 @@ fn parse_feature_requirements( if key == Feature::Personality.key() { continue; } - if matches!( - key.as_str(), - "daemon_auto_start" | "worktrees" | "realtime_conversation" - ) { + if key == Feature::Worktrees.key() || key == Feature::RealtimeConversation.key() { 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 dce4872a16..f76315c675 100644 --- a/codex-rs/features/src/lib.rs +++ b/codex-rs/features/src/lib.rs @@ -122,7 +122,7 @@ pub enum Feature { /// Store CLI auth in the encrypted local secrets backend when keyring storage is selected. SecretAuthStorage, - /// Removed compatibility flag. Eligible interactive launches always start or reuse the daemon. + /// Automatically start the shared local daemon for eligible interactive launches. DaemonAutoStart, /// Allow the TUI to discover and select among multiple conversation backends. TuiMultiBackend, @@ -738,13 +738,6 @@ 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; @@ -868,10 +861,6 @@ 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()), @@ -1136,7 +1125,7 @@ pub const FEATURES: &[FeatureSpec] = &[ FeatureSpec { id: Feature::DaemonAutoStart, key: "daemon_auto_start", - stage: Stage::Removed, + stage: Stage::Stable, 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 8a48eb9031..681698d984 100644 --- a/codex-rs/tui/src/bottom_pane/experimental_features_view.rs +++ b/codex-rs/tui/src/bottom_pane/experimental_features_view.rs @@ -282,10 +282,7 @@ impl BottomPaneView for ExperimentalFeaturesView { let mut count = 0; for feature in features { if feature.stage != ExperimentalFeatureStage::Beta - || matches!( - feature.name.as_str(), - "daemon_auto_start" | "worktrees" | "realtime_conversation" - ) + || matches!(feature.name.as_str(), "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 3e8122fef2..c3a0ff6a92 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,7 +104,6 @@ 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 bf4b7bd895..270a24c4f8 100644 --- a/codex-rs/tui/src/experimental_features.rs +++ b/codex-rs/tui/src/experimental_features.rs @@ -109,7 +109,8 @@ pub(crate) async fn write( let key = format!("features.{}", serde_json::json!(name)); Ok(crate::config_update::replace_config_value( key, - if *enabled || feature.default_enabled { + // Keep the daemon opt-out explicit even before rollout defaults enable it. + if *enabled || feature.default_enabled || name == "daemon_auto_start" { 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 a8a64fa610..1e41076579 100644 --- a/codex-rs/tui/src/experimental_features_tests.rs +++ b/codex-rs/tui/src/experimental_features_tests.rs @@ -181,6 +181,7 @@ 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} @@ -191,6 +192,7 @@ 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"} @@ -227,6 +229,7 @@ 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 deleted file mode 100644 index 04ba91cf33..0000000000 --- a/codex-rs/tui/src/history_cell/snapshots/codex_tui__history_cell__tests__daemon_auto_start_migration_notice.snap +++ /dev/null @@ -1,8 +0,0 @@ ---- -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 8c12d0e88e..d0e70b0574 100644 --- a/codex-rs/tui/src/history_cell/tests.rs +++ b/codex-rs/tui/src/history_cell/tests.rs @@ -2973,26 +2973,15 @@ fn deprecation_notice_renders_summary_with_details() { } #[test] -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") - ); - } +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")); } #[test] diff --git a/codex-rs/tui/src/startup_orchestration.rs b/codex-rs/tui/src/startup_orchestration.rs index 8e7076ab5b..d2bf6ba9b2 100644 --- a/codex-rs/tui/src/startup_orchestration.rs +++ b/codex-rs/tui/src/startup_orchestration.rs @@ -568,6 +568,7 @@ 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(); @@ -732,7 +733,7 @@ pub(super) async fn run_main_inner( (_, Some(_)) => "incompatible_option", _ if auto_start_daemon => "auto_start", (AppServerTarget::LocalDaemon { .. }, _) => "existing_daemon", - (AppServerTarget::Embedded, None) => "unclassified", + (AppServerTarget::Embedded, None) => "auto_start_disabled", }; let daemon_settings = if metrics.is_some() { codex_app_server_daemon::telemetry::settings_tags(&config.codex_home) @@ -743,7 +744,17 @@ pub(super) async fn run_main_inner( }; let mut launch_tags = daemon_settings.to_vec(); launch_tags.extend(terminal_size_tags); - launch_tags.push(("daemon_selection_reason", selection_reason)); + launch_tags.extend([ + ("daemon_selection_reason", selection_reason), + ( + "daemon_auto_start", + if config.features.enabled(Feature::DaemonAutoStart) { + "enabled" + } else { + "disabled" + }, + ), + ]); let selected_local_daemon = matches!(app_server_target, AppServerTarget::LocalDaemon { .. }); let launch_telemetry = move |app_server_mode| { let Some(metrics) = metrics else { @@ -803,6 +814,9 @@ 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 d135574836..86e8b77910 100644 --- a/codex-rs/tui/tests/suite/daemon_compatibility.rs +++ b/codex-rs/tui/tests/suite/daemon_compatibility.rs @@ -21,43 +21,37 @@ 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 { - // 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 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; }; - 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": [{ + 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": [{ "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 2539c6e08e..0cfbfc252a 100644 --- a/codex-rs/tui/tests/suite/directory_trust.rs +++ b/codex-rs/tui/tests/suite/directory_trust.rs @@ -39,7 +39,6 @@ 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)); @@ -49,7 +48,6 @@ 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 { @@ -76,7 +74,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/0.0.0", "codexHome": server_home, "platformFamily": std::env::consts::FAMILY, "platformOs": std::env::consts::OS}), + "initialize" => json!({"userAgent": "trust-pty"}), "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 58f32a5154..084599ce1c 100644 --- a/codex-rs/tui/tests/suite/focus_palette.rs +++ b/codex-rs/tui/tests/suite/focus_palette.rs @@ -35,8 +35,7 @@ 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-daemon", "--no-alt-screen"])?; + let mut terminal = PtyCodex::start(&repo_root, codex_home, &["--no-alt-screen"])?; terminal.wait_for_startup()?; let startup_output_len = terminal.output.len(); @@ -166,7 +165,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, &["--no-daemon"])?; + let mut terminal = PtyCodex::start(&repo_root, codex_home, &[])?; terminal.wait_for_startup()?; let deadline = Instant::now() + STARTUP_TIMEOUT; while !terminal.parser.screen().alternate_screen() && Instant::now() < deadline { @@ -281,7 +280,7 @@ fn fullscreen_transcript_can_opt_out_to_terminal_scrollback() -> Result<()> { let mut terminal = PtyCodex::start( &repo_root, codex_home, - &["--no-daemon", "-c", "tui.fullscreen_transcript=false"], + &["-c", "tui.fullscreen_transcript=false"], )?; terminal.wait_for_startup()?; terminal.wait_for_screen("GPT-5.6-Terra")?; @@ -571,6 +570,7 @@ 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,6 +589,12 @@ 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 { @@ -631,6 +637,12 @@ 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 a20cb39d20..1677ba7b81 100644 --- a/codex-rs/tui/tests/suite/reconnect.rs +++ b/codex-rs/tui/tests/suite/reconnect.rs @@ -41,7 +41,6 @@ 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); @@ -53,8 +52,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}] }); - // Startup probes the running daemon and checks compatibility before opening the session. - for connection in -2..7 { + // The first connection checks daemon compatibility before TUI startup. + for connection in -1..7 { let mut socket = loop { let (stream, _) = listener.accept().await?; // Startup probes the default daemon socket before opening its WebSocket. @@ -106,9 +105,7 @@ async fn automatic_reconnect_restores_draft_and_routes_new_notifications() -> Re continue; } let result = match request.method.as_str() { - "initialize" => { - json!({"userAgent": "reconnect-pty/0.0.0", "codexHome": server_home, "platformFamily": std::env::consts::FAMILY, "platformOs": std::env::consts::OS}) - } + "initialize" => json!({"userAgent": "reconnect-pty"}), // 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 7207e6174e..5c6195d499 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, &["--no-daemon"])?; + let mut terminal = PtyCodex::start(&repo_root, home, &[])?; 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 604f4fd4db..88945a613d 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, &["--no-daemon"])?; + let mut terminal = PtyCodex::start(&root, home, &[])?; 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 6c7560f932..ecec4a8955 100644 --- a/codex-rs/tui/tests/suite/worktree_stack.rs +++ b/codex-rs/tui/tests/suite/worktree_stack.rs @@ -99,7 +99,6 @@ 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",