[codex] preserve explicit environment cwd (#27995)

## Why

`TurnEnvironmentSelections::new` rewrote the primary environment's
explicit `cwd` to the legacy fallback cwd. For a remote-first selection,
this could replace the remote working directory with a local fallback
path and made the legacy cwd overlay authoritative over
environment-owned state.

## What changed

- Preserve every explicit environment cwd when constructing turn
environment selections.
- Keep `cwd`-only app-server updates compatible by rebuilding the
default environment selections at the requested cwd.
- Cover both explicit primary cwd preservation and cwd-only updates
reaching the model-visible execution environment.

## Testing

- `just test -p codex-core
session_update_settings_does_not_rewrite_sticky_environment_cwds`
- `just test -p codex-core
environment_settings_preserve_explicit_primary_cwd`
- `just test -p codex-app-server
thread_settings_update_cwd_retargets_default_environment`
This commit is contained in:
pakrym-oai authored and GitHub committed 2026-06-15 17:17:34 +00:00
1 parent 495da45643
commit bd4fe31c5a
4 files changed
+99 -47

No files matched your search

@@ -435,8 +435,9 @@ impl TurnRequestProcessor {
let additional_context = map_additional_context(params.additional_context);
let turn_has_input = !mapped_items.is_empty();
let cwd = resolve_request_cwd(params.cwd)?;
let environments =
Self::build_environment_override(thread.as_ref(), cwd, environment_selections).await;
let environments = self
.build_environment_override(thread.as_ref(), cwd, environment_selections)
.await;
let thread_settings = self
.build_thread_settings_overrides(
thread.as_ref(),
@@ -509,28 +510,36 @@ impl TurnRequestProcessor {
}
async fn build_environment_override(
&self,
thread: &CodexThread,
cwd: Option<AbsolutePathBuf>,
environment_selections: Option<Vec<TurnEnvironmentSelection>>,
) -> Option<TurnEnvironmentSelections> {
if cwd.is_none() && environment_selections.is_none() {
return None;
match (cwd, environment_selections) {
(None, None) => None,
(Some(cwd), None) => {
let environment_selections =
self.thread_manager.default_environment_selections(&cwd);
Some(TurnEnvironmentSelections::new(cwd, environment_selections))
}
(cwd, Some(environment_selections)) => {
let legacy_fallback_cwd = match cwd {
Some(cwd) => cwd,
None => {
let snapshot = thread.config_snapshot().await;
environment_selections
.iter()
.find(|selection| selection.environment_id == LOCAL_ENVIRONMENT_ID)
.and_then(|selection| selection.cwd.to_abs_path().ok())
.unwrap_or_else(|| snapshot.cwd().clone())
}
};
Some(TurnEnvironmentSelections::new(
legacy_fallback_cwd,
environment_selections,
))
}
}
let snapshot = thread.config_snapshot().await;
let environment_selections =
environment_selections.unwrap_or_else(|| snapshot.environment_selections().to_vec());
let legacy_fallback_cwd = cwd.unwrap_or_else(|| {
environment_selections
.iter()
.find(|selection| selection.environment_id == LOCAL_ENVIRONMENT_ID)
.and_then(|selection| selection.cwd.to_abs_path().ok())
.unwrap_or_else(|| snapshot.cwd().clone())
});
Some(TurnEnvironmentSelections::new(
legacy_fallback_cwd,
environment_selections,
))
}
async fn build_thread_settings_overrides(
@@ -694,12 +703,9 @@ impl TurnRequestProcessor {
) -> Result<ThreadSettingsUpdateResponse, JSONRPCErrorError> {
let (_, thread) = self.load_thread(&params.thread_id).await?;
let cwd = resolve_request_cwd(params.cwd)?;
let environments = Self::build_environment_override(
thread.as_ref(),
cwd,
/*environment_selections*/ None,
)
.await;
let environments = self
.build_environment_override(thread.as_ref(), cwd, /*environment_selections*/ None)
.await;
let thread_settings = self
.build_thread_settings_overrides(
thread.as_ref(),