app-server: preserve target-native environment cwd (#28146)

## Why

app-server may run on a different OS from the selected exec-server
environment. Parsing that environment’s cwd with the Codex host’s path
rules prevents thread startup.

## What

Carry environment cwd values as `LegacyAppPathString` at the app-server
boundary and `PathUri` internally. Existing tool-call schemas and
relative-path behavior stay host-native; remaining local-only consumers
convert explicitly and leave follow-up TODOs.

The Wine integration test verifies app-server can start a thread and
complete an ordinary turn with a Windows environment cwd from Linux.

## Validation

- `bazel test //codex-rs/core/tests/remote_env_windows:smoke-test
--test_output=errors`
- focused app-server environment-selection and protocol schema tests
- scoped Clippy for `codex-core` and `codex-app-server-protocol`
This commit is contained in:
Adam Perry @ OpenAI authored and GitHub committed 2026-06-16 21:42:28 +00:00
1 parent 33d50234a8
commit f8850cab1d
37 files changed
+346 -208

No files matched your search

+8 -20
View File
@@ -3,7 +3,6 @@ use crate::SkillLoadOutcome;
use crate::agents_md::LoadedAgentsMd;
use crate::config::GhostSnapshotConfig;
use crate::environment_selection::TurnEnvironmentSnapshot;
use crate::path_utils;
use crate::shell_snapshot::ShellSnapshotFile;
use codex_core_skills::HostLoadedSkills;
use codex_file_system::FileSystemSandboxContext;
@@ -49,13 +48,7 @@ pub(crate) type ShellSnapshotTask = Shared<BoxFuture<'static, Option<Arc<ShellSn
pub(crate) struct TurnEnvironment {
pub(crate) environment_id: String,
pub(crate) environment: Arc<Environment>,
// Keep both representations together while cwd consumers migrate to URI semantics. Keeping
// them synchronized means neither representation can be exposed through a mutable reference;
// updates must rebuild the validated pair through `TurnEnvironment::new`. Once
// `TurnEnvironment::cwd` itself becomes a `PathUri`, convert only at native filesystem and
// process-launch boundaries and remove this paired migration state.
cwd: AbsolutePathBuf,
cwd_uri: PathUri,
cwd: PathUri,
pub(crate) shell: Option<shell::Shell>,
pub(crate) shell_snapshot: ShellSnapshotTask,
}
@@ -64,22 +57,20 @@ impl TurnEnvironment {
pub(crate) fn new(
environment_id: String,
environment: Arc<Environment>,
cwd: AbsolutePathBuf,
cwd: PathUri,
shell: Option<shell::Shell>,
) -> Self {
let cwd_uri = PathUri::from_abs_path(&cwd);
Self {
environment_id,
environment,
cwd,
cwd_uri,
shell,
shell_snapshot: futures::future::ready(None).boxed().shared(),
}
}
pub(crate) fn shell_snapshot(&self, cwd: &AbsolutePathBuf) -> Option<AbsolutePathBuf> {
if !path_utils::paths_match_after_normalization(self.cwd.as_path(), cwd.as_path()) {
if self.cwd != PathUri::from_abs_path(cwd) {
return None;
}
self.shell_snapshot
@@ -88,18 +79,14 @@ impl TurnEnvironment {
.map(ShellSnapshotFile::path)
}
pub(crate) fn cwd(&self) -> &AbsolutePathBuf {
pub(crate) fn cwd(&self) -> &PathUri {
&self.cwd
}
pub(crate) fn cwd_uri(&self) -> &PathUri {
&self.cwd_uri
}
pub(crate) fn selection(&self) -> TurnEnvironmentSelection {
TurnEnvironmentSelection {
environment_id: self.environment_id.clone(),
cwd: self.cwd_uri.clone(),
cwd: self.cwd.clone(),
}
}
}
@@ -110,7 +97,6 @@ impl std::fmt::Debug for TurnEnvironment {
.field("environment_id", &self.environment_id)
.field("environment", &self.environment)
.field("cwd", &self.cwd)
.field("cwd_uri", &self.cwd_uri)
.field("shell", &self.shell)
.finish_non_exhaustive()
}
@@ -755,9 +741,11 @@ impl Session {
) -> Arc<TurnContext> {
let turn_environments = self.services.turn_environments.snapshot().await;
let primary_turn_environment = turn_environments.primary().cloned();
// TODO(anp): Migrate per-turn config and legacy TurnContext cwd consumers to PathUri so
// a foreign primary environment does not fall back to the session's host cwd.
let cwd = primary_turn_environment
.as_ref()
.map(|turn_environment| turn_environment.cwd().clone())
.and_then(|turn_environment| turn_environment.cwd().to_abs_path().ok())
.unwrap_or_else(|| session_configuration.cwd().clone());
let per_turn_config = Self::build_per_turn_config(&session_configuration, cwd.clone());
{