From 88ef37c64974e1209411f6d449b0db942b3eb731 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Sun, 20 Sep 2026 23:07:15 +0000 Subject: [PATCH] Identify local background servers in `/status` (#46905) ## What changed Rename the `/status` connection row from `Remote` to `Server`. For local daemon connections, display `Local background server` instead of the socket address and version. Keep the address and version for remote connections, including explicitly configured Unix sockets. ## Testing Add a local background server snapshot and coverage distinguishing local daemon connections from remote Unix sockets. Update daemon startup and worktree tests to expect the new label. GitOrigin-RevId: 9460415fe1ec16d90f575917c1a4ce1bf19faaaf --- codex-rs/cli/tests/daemon_startup.rs | 3 +- codex-rs/cli/tests/worktree.rs | 2 +- codex-rs/tui/src/app/daemon_menu_tests.rs | 2 + .../tests/misalignment_policy_tests.rs | 1 + .../chatwidget/tests/status_command_tests.rs | 1 + codex-rs/tui/src/status/card.rs | 14 ++-- codex-rs/tui/src/status/remote_connection.rs | 22 +++++- ...atus_snapshot_local_background_server.snap | 23 ++++++ ...s_default_reasoning_when_config_empty.snap | 2 +- codex-rs/tui/src/status/tests.rs | 74 +++++++++++-------- 10 files changed, 101 insertions(+), 43 deletions(-) create mode 100644 codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_local_background_server.snap diff --git a/codex-rs/cli/tests/daemon_startup.rs b/codex-rs/cli/tests/daemon_startup.rs index d61f36fb22..3073e1d127 100644 --- a/codex-rs/cli/tests/daemon_startup.rs +++ b/codex-rs/cli/tests/daemon_startup.rs @@ -117,7 +117,7 @@ async fn daemon_startup(command: &str) -> Result<()> { let expected = if command == "start" { // The draft header is visible before the session's command composer is ready. steps.push_back(("GPT-5.6-Terra", b"/status\r")); - "app-server-control.sock" + "Server:Localbackgroundserver" } else if bedrock_onboarding { "UseAmazonBedrock" } else { @@ -177,7 +177,6 @@ async fn daemon_startup(command: &str) -> Result<()> { output.clear(); } else if steps.is_empty() && text.contains(expected) { if command == "start" { - ensure!(text.contains("unix://")); ensure!(home.path().join("app-server-daemon/daemon.pid").exists()); } else if let Some(existing_daemon) = &existing_daemon { ensure!(fs::read(&pid_file)? == *existing_daemon); diff --git a/codex-rs/cli/tests/worktree.rs b/codex-rs/cli/tests/worktree.rs index 2fea780147..a15817ef4e 100644 --- a/codex-rs/cli/tests/worktree.rs +++ b/codex-rs/cli/tests/worktree.rs @@ -498,7 +498,7 @@ trust_level = "trusted" if backend == "daemon" && !analytics && matches!(observed, Ok(Ok(_))) { session.writer_sender().send(b"/status\r".to_vec()).await?; tokio::time::timeout(Duration::from_secs(/*secs*/ 10), async { - while !output.contains("app-server-control.sock") { + while !output.contains("Local background server") { let bytes = stdout .recv() .await diff --git a/codex-rs/tui/src/app/daemon_menu_tests.rs b/codex-rs/tui/src/app/daemon_menu_tests.rs index 59c4cc773f..0928bf31f0 100644 --- a/codex-rs/tui/src/app/daemon_menu_tests.rs +++ b/codex-rs/tui/src/app/daemon_menu_tests.rs @@ -82,6 +82,7 @@ async fn daemon_menu_is_read_only_and_confirmation_can_cancel_or_handoff() { app.chat_widget.remote_connection = Some(RemoteConnectionStatus { address: "local".into(), version: "v0.153.0".into(), + is_local_daemon: true, }); app.initialize_server_version_notice("0.154.0", Some("0.153.0")); let view = app.agents_overview_view(Vec::new(), /*selected_thread_id*/ None); @@ -213,6 +214,7 @@ async fn unavailable_daemon_menu_offers_guidance_without_update_actions() { app.chat_widget.remote_connection = Some(RemoteConnectionStatus { address: "ws://example.test:1234".into(), version: "v0.153.0".into(), + is_local_daemon: false, }); app.open_daemon_menu(); insta::assert_snapshot!( diff --git a/codex-rs/tui/src/chatwidget/tests/misalignment_policy_tests.rs b/codex-rs/tui/src/chatwidget/tests/misalignment_policy_tests.rs index 4516460017..6037019420 100644 --- a/codex-rs/tui/src/chatwidget/tests/misalignment_policy_tests.rs +++ b/codex-rs/tui/src/chatwidget/tests/misalignment_policy_tests.rs @@ -155,6 +155,7 @@ async fn misalignment_policy_failure_stops_the_thread_and_renders_once() { chat.remote_connection = Some(crate::status::remote_connection::RemoteConnectionStatus { address: "wss://remote.example.com".to_string(), version: "v1.0.0".to_string(), + is_local_daemon: false, }); chat.show_misalignment_policy_precaution(); assert_chatwidget_snapshot!( diff --git a/codex-rs/tui/src/chatwidget/tests/status_command_tests.rs b/codex-rs/tui/src/chatwidget/tests/status_command_tests.rs index a6036045ea..75685a2581 100644 --- a/codex-rs/tui/src/chatwidget/tests/status_command_tests.rs +++ b/codex-rs/tui/src/chatwidget/tests/status_command_tests.rs @@ -532,6 +532,7 @@ async fn status_command_requests_thread_usage_for_remote_connection_metadata() { chat.remote_connection = Some(crate::status::remote_connection::RemoteConnectionStatus { address: "wss://remote.example.com".to_string(), version: "v1.0.0".to_string(), + is_local_daemon: false, }); chat.dispatch_command(SlashCommand::Status); diff --git a/codex-rs/tui/src/status/card.rs b/codex-rs/tui/src/status/card.rs index 043eef62cd..369a6749b2 100644 --- a/codex-rs/tui/src/status/card.rs +++ b/codex-rs/tui/src/status/card.rs @@ -824,18 +824,20 @@ impl StatusHistoryCell { lines.push(Line::from(Vec::>::new())); } if let Some(remote_connection) = self.remote_connection.as_ref() { - let wrapped_remote = word_wrap_lines( - [Line::from(vec![ + let value = if remote_connection.is_local_daemon { + Line::from("Local background server") + } else { + Line::from(vec![ Span::from(remote_connection.address.clone()), Span::from(" (").dim(), Span::from(remote_connection.version.clone()).dim(), Span::from(")").dim(), - ])], - RtOptions::new(value_width.max(1)), - ); + ]) + }; + let wrapped_remote = word_wrap_lines([value], RtOptions::new(value_width.max(1))); let mut wrapped_remote = wrapped_remote.into_iter(); if let Some(first) = wrapped_remote.next() { - lines.push(formatter.line("Remote", first.spans)); + lines.push(formatter.line("Server", first.spans)); lines.extend(wrapped_remote.map(|line| formatter.continuation(line.spans))); } lines.push(Line::from(Vec::>::new())); diff --git a/codex-rs/tui/src/status/remote_connection.rs b/codex-rs/tui/src/status/remote_connection.rs index 130baa4b9a..6ed6fdd411 100644 --- a/codex-rs/tui/src/status/remote_connection.rs +++ b/codex-rs/tui/src/status/remote_connection.rs @@ -9,6 +9,7 @@ use url::Url; pub(crate) struct RemoteConnectionStatus { pub(crate) address: String, pub(crate) version: String, + pub(crate) is_local_daemon: bool, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -40,7 +41,11 @@ pub(crate) fn remote_connection_status_value( let version = server_version .map(|version| format!("v{version}")) .unwrap_or_else(|| "unknown".to_string()); - Some(RemoteConnectionStatus { address, version }) + Some(RemoteConnectionStatus { + address, + version, + is_local_daemon: matches!(app_server_target, AppServerTarget::LocalDaemon { .. }), + }) } pub(crate) fn server_version_notice(client: &str, server: Option<&str>) -> Option { @@ -187,6 +192,7 @@ mod tests { Some(RemoteConnectionStatus { address: "ws://127.0.0.1:4500/".to_string(), version: "v1.2.3".to_string(), + is_local_daemon: false, }) ); @@ -202,6 +208,20 @@ mod tests { Some(RemoteConnectionStatus { address: format!("unix://{}", socket_path.display()), version: "unknown".to_string(), + is_local_daemon: true, + }) + ); + let remote_socket_target = AppServerTarget::Remote { + endpoint: RemoteAppServerEndpoint::UnixSocket { + socket_path: socket_path.clone(), + }, + }; + assert_eq!( + remote_connection_status_value(&remote_socket_target, Some("1.2.3")), + Some(RemoteConnectionStatus { + address: format!("unix://{}", socket_path.display()), + version: "v1.2.3".to_string(), + is_local_daemon: false, }) ); Ok(()) diff --git a/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_local_background_server.snap b/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_local_background_server.snap new file mode 100644 index 0000000000..7e65d321ce --- /dev/null +++ b/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_local_background_server.snap @@ -0,0 +1,23 @@ +--- +source: tui/src/status/tests.rs +expression: sanitized +--- +/status + +╭─────────────────────────────────────────────────────────────────────────────╮ +│ >_ OpenAI Codex (v0.0.0) │ +│ │ +│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │ +│ information on rate limits and credits │ +│ │ +│ Server: Local background server │ +│ │ +│ Model: gpt-5.1-codex-max (reasoning medium, summaries auto) │ +│ Directory: [[workspace]] │ +│ Permissions: Custom (workspace with network access, Ask for approval) │ +│ Agents.md: │ +│ │ +│ Token usage: 750 total (500 input + 250 output) │ +│ Context window: 100% left (750 used / 272K) │ +│ Limits: data not available yet │ +╰─────────────────────────────────────────────────────────────────────────────╯ diff --git a/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_uses_default_reasoning_when_config_empty.snap b/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_uses_default_reasoning_when_config_empty.snap index 9683beed97..5eefeb85ea 100644 --- a/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_uses_default_reasoning_when_config_empty.snap +++ b/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_uses_default_reasoning_when_config_empty.snap @@ -10,7 +10,7 @@ expression: sanitized │ Visit https://chatgpt.com/codex/settings/usage for up-to-date │ │ information on rate limits and credits │ │ │ -│ Remote: unix:///tmp/codex-home/app-server-control/app-server- │ +│ Server: unix:///tmp/codex-home/app-server-control/app-server- │ │ control.sock (v0.133.0) │ │ │ │ Model: gpt-5.1-codex-max (reasoning medium, summaries auto) │ diff --git a/codex-rs/tui/src/status/tests.rs b/codex-rs/tui/src/status/tests.rs index f9384badc9..8c75176aa3 100644 --- a/codex-rs/tui/src/status/tests.rs +++ b/codex-rs/tui/src/status/tests.rs @@ -1638,41 +1638,51 @@ async fn status_snapshot_uses_default_reasoning_when_config_empty() { .with_ymd_and_hms(2024, 2, 3, 4, 5, 6) .single() .expect("timestamp"); - let remote_connection = RemoteConnectionStatus { - address: "unix:///tmp/codex-home/app-server-control/app-server-control.sock".to_string(), - version: "v0.133.0".to_string(), - }; + for (is_local_daemon, snapshot) in [ + ( + false, + "status_snapshot_uses_default_reasoning_when_config_empty", + ), + (true, "status_snapshot_local_background_server"), + ] { + let remote_connection = RemoteConnectionStatus { + address: "unix:///tmp/codex-home/app-server-control/app-server-control.sock" + .to_string(), + version: "v0.133.0".to_string(), + is_local_daemon, + }; - let model_slug = get_model_offline_for_tests(config.model.as_deref()); - let token_info = token_info_for(&model_slug, &config, &usage); - let (composite, _) = new_status_output_with_rate_limits_handle( - &config, - /*requires_openai_auth*/ true, - /*model_provider_id*/ None, - Some(&remote_connection), - account_display.as_ref(), - Some(&token_info), - &usage, - &None, - /*thread_name*/ None, - /*forked_from*/ None, - &[], - None, - now, - &model_slug, - /*collaboration_mode*/ None, - /*reasoning_effort_override*/ Some(Some(ReasoningEffort::Medium)), - "".to_string(), - /*refreshing_rate_limits*/ false, - ); - let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 80)); - if cfg!(windows) { - for line in &mut rendered_lines { - *line = line.replace('\\', "/"); + let model_slug = get_model_offline_for_tests(config.model.as_deref()); + let token_info = token_info_for(&model_slug, &config, &usage); + let (composite, _) = new_status_output_with_rate_limits_handle( + &config, + /*requires_openai_auth*/ true, + /*model_provider_id*/ None, + Some(&remote_connection), + account_display.as_ref(), + Some(&token_info), + &usage, + &None, + /*thread_name*/ None, + /*forked_from*/ None, + &[], + None, + now, + &model_slug, + /*collaboration_mode*/ None, + /*reasoning_effort_override*/ Some(Some(ReasoningEffort::Medium)), + "".to_string(), + /*refreshing_rate_limits*/ false, + ); + let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 80)); + if cfg!(windows) { + for line in &mut rendered_lines { + *line = line.replace('\\', "/"); + } } + let sanitized = sanitize_directory(rendered_lines).join("\n"); + assert_snapshot!(snapshot, sanitized); } - let sanitized = sanitize_directory(rendered_lines).join("\n"); - assert_snapshot!(sanitized); } #[tokio::test]