core: require explicit cwd for foreign defaults

This commit is contained in:
Adam Perry
2026-06-13 07:07:01 +00:00
parent eb88561c3f
commit c8ee960fe0
4 changed files with 60 additions and 6 deletions

View File

@@ -682,7 +682,7 @@ Turns attach user input (text or images) to a thread and trigger Codex generatio
- `{"type":"image","url":"https://…png"}`
- `{"type":"localImage","path":"/tmp/screenshot.png"}`
You can optionally specify config overrides on the new turn. If specified, these settings become the default for subsequent turns on the same thread. `outputSchema` applies only to the current turn. Experimental `environments` is turn-scoped: omit it to inherit the thread's sticky environments, pass `[]` to run the turn with no environments, or pass explicit environment ids to override the sticky selection for this turn only. Each environment `cwd` must be absolute and use that environment's native path convention, so a Windows environment uses a value such as `C:\\workspace` even when app-server runs on Linux. The path must also be portable to the selected executor; app-server rejects malformed paths and Windows device names before starting the thread or turn.
You can optionally specify config overrides on the new turn. If specified, these settings become the default for subsequent turns on the same thread. `outputSchema` applies only to the current turn. Experimental `environments` is turn-scoped: omit it to inherit the thread's sticky environments, pass `[]` to run the turn with no environments, or pass explicit environment ids to override the sticky selection for this turn only. Each environment `cwd` must be absolute and use that environment's native path convention, so a Windows environment uses a value such as `C:\\workspace` even when app-server runs on Linux. The path must also be portable to the selected executor; app-server rejects malformed paths and Windows device names before starting the thread or turn. If a default environment uses a different path convention from app-server, clients must provide an explicit environment `cwd`.
`approvalsReviewer` accepts:

View File

@@ -212,6 +212,28 @@ url = "ws://127.0.0.1:8765"
assert!(err.to_string().contains("duplicate"));
}
#[cfg(unix)]
#[test]
fn foreign_remote_environment_requires_explicit_native_cwd() {
let environment = Arc::new(
Environment::create_for_tests(Some("ws://127.0.0.1:8765".to_string()))
.expect("remote environment"),
);
let error = TurnEnvironment::new_with_uri(
REMOTE_ENVIRONMENT_ID.to_string(),
environment,
PathUri::parse("file:///workspace").expect("POSIX cwd URI"),
codex_utils_path_uri::PathConvention::Windows,
crate::shell::default_user_shell(),
)
.expect_err("host cwd must not be projected into a foreign environment");
assert_eq!(
error.to_string(),
"explicit environment cwd required for foreign environment `remote` using Windows path syntax"
);
}
#[tokio::test]
async fn resolved_environment_selections_use_first_selection_as_primary() {
let cwd = AbsolutePathBuf::current_dir().expect("cwd");

View File

@@ -73,11 +73,19 @@ impl TurnEnvironment {
path_convention: PathConvention,
shell: shell::Shell,
) -> CodexResult<Self> {
ApiPathString::from_path_uri(&cwd, path_convention).map_err(|err| {
CodexErr::InvalidRequest(format!(
"turn environment cwd `{cwd}` cannot be rendered for {path_convention}: {err}"
))
})?;
match ApiPathString::from_path_uri(&cwd, path_convention) {
Ok(_) => {}
Err(_) if environment.is_remote() && path_convention != PathConvention::native() => {
return Err(CodexErr::InvalidRequest(format!(
"explicit environment cwd required for foreign environment `{environment_id}` using {path_convention} path syntax"
)));
}
Err(err) => {
return Err(CodexErr::InvalidRequest(format!(
"turn environment cwd `{cwd}` cannot be rendered for {path_convention}: {err}"
)));
}
}
Ok(Self {
environment_id,
environment,

View File

@@ -163,6 +163,30 @@ async fn exercise_app_server(websocket_url: String) -> Result<()> {
"invalid environment cwd must fail before model inference"
);
let default_request_id = app_server
.send_thread_start_request(ThreadStartParams {
model: Some("mock-model".to_string()),
..Default::default()
})
.await?;
let default_response: JSONRPCError = timeout(
APP_SERVER_TIMEOUT,
app_server.read_stream_until_error_message(RequestId::Integer(default_request_id)),
)
.await??;
assert_eq!(default_response.id, RequestId::Integer(default_request_id));
assert_eq!(default_response.error.code, -32600);
assert_eq!(
default_response.error.message,
format!(
"explicit environment cwd required for foreign environment `{REMOTE_ENVIRONMENT_ID}` using Windows path syntax"
)
);
assert!(
response_mock.requests().is_empty(),
"foreign default cwd must fail before model inference"
);
let environment = remote_windows_environment();
let thread_request_id = app_server
.send_thread_start_request(ThreadStartParams {