From 66f0220c56803874b4ac905cf0aaae61d6af6513 Mon Sep 17 00:00:00 2001 From: Rasmus Rygaard Date: Tue, 23 Jun 2026 13:39:13 -0700 Subject: [PATCH] [codex] Report the exec-server working directory (#29666) ## Summary - add the exec-server working directory to `environment/info` as an optional `PathUri` - populate it from the executor process's current directory - preserve compatibility with older responses that omit `cwd` ## Why Remote clients currently have no executor-native default working directory. This forces callers such as app-server-backend to assume `/workspace`, which fails for laptop environments. Reporting the cwd alongside the detected shell lets clients use the path convention and location of the actual executor. ## Impact This is backward-compatible: the new response field is optional, and clients can continue handling responses from older exec servers. A follow-up app-server-backend change will consume the value for cwd-less `command/exec` requests. ## Validation - `just test -p codex-exec-server` (275 passed, 2 skipped) --- codex-rs/exec-server/src/environment.rs | 17 +++++++++++++++++ codex-rs/exec-server/src/protocol.rs | 24 ++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/codex-rs/exec-server/src/environment.rs b/codex-rs/exec-server/src/environment.rs index ee0e303ca..b1e4ae5c5 100644 --- a/codex-rs/exec-server/src/environment.rs +++ b/codex-rs/exec-server/src/environment.rs @@ -28,6 +28,7 @@ use crate::remote::NoiseRendezvousEnvironmentConfig; use crate::remote_file_system::RemoteFileSystem; use crate::remote_process::RemoteProcess; use codex_shell_command::shell_detect::DetectedShell; +use codex_utils_path_uri::PathUri; use tokio_util::task::AbortOnDropHandle; pub const CODEX_EXEC_SERVER_URL_ENV_VAR: &str = "CODEX_EXEC_SERVER_URL"; @@ -608,6 +609,9 @@ impl EnvironmentInfo { pub(crate) fn local() -> Self { Self { shell: codex_shell_command::shell_detect::default_user_shell().into(), + cwd: std::env::current_dir() + .ok() + .and_then(|cwd| PathUri::from_host_native_path(cwd).ok()), } } } @@ -655,6 +659,19 @@ mod tests { assert!(manager.try_local_environment().is_none()); } + #[test] + fn local_environment_info_includes_current_directory() { + let info = super::EnvironmentInfo::local(); + + assert_eq!( + info.cwd, + Some( + PathUri::from_host_native_path(std::env::current_dir().expect("current directory")) + .expect("cwd URI") + ) + ); + } + #[tokio::test] async fn noise_environment_config_selects_remote_as_default() { let config = noise_environment_config_from_values( diff --git a/codex-rs/exec-server/src/protocol.rs b/codex-rs/exec-server/src/protocol.rs index 921223e31..9c1a6398a 100644 --- a/codex-rs/exec-server/src/protocol.rs +++ b/codex-rs/exec-server/src/protocol.rs @@ -72,6 +72,9 @@ pub struct InitializeResponse { #[serde(rename_all = "camelCase")] pub struct EnvironmentInfo { pub shell: ShellInfo, + /// Working directory inherited by the exec-server process. + #[serde(default)] + pub cwd: Option, } /// Shell detected for an execution/filesystem environment. @@ -511,10 +514,12 @@ mod base64_bytes { #[cfg(test)] mod tests { + use super::EnvironmentInfo; use super::ExecParams; use super::FsReadFileParams; use super::HttpRequestParams; use super::ProcessId; + use super::ShellInfo; use codex_file_system::FileSystemSandboxContext; use codex_network_proxy::ManagedNetworkSandboxContext; use codex_protocol::models::PermissionProfile; @@ -566,6 +571,25 @@ mod tests { assert_eq!(legacy.managed_network, None); } + #[test] + fn environment_info_accepts_legacy_response_without_cwd() { + let info: EnvironmentInfo = serde_json::from_value(serde_json::json!({ + "shell": { "name": "zsh", "path": "/bin/zsh" } + })) + .expect("legacy environment info should deserialize"); + + assert_eq!( + info, + EnvironmentInfo { + shell: ShellInfo { + name: "zsh".to_string(), + path: "/bin/zsh".to_string(), + }, + cwd: None, + } + ); + } + #[test] fn filesystem_protocol_accepts_legacy_absolute_paths_and_serializes_path_uris() { let legacy_path = std::env::current_dir()