sandboxing: migrate cwd inputs to PathUri (#27816)

## Why

Sandbox cwd values can cross app-server and exec-server host boundaries.
They should retain URI semantics until the receiving host validates them
instead of being interpreted early as native paths.

## What

- Carry `PathUri` through filesystem sandbox contexts, sandbox commands,
and transform inputs.
- Convert command and policy cwd once in `SandboxManager::transform`,
then keep launch requests native.
- Preserve sandbox cwd over remote filesystem transport and reject
non-native URIs without fallback.
- Cache paired native/URI turn-environment cwd values during migration,
with immutable access to keep them synchronized.
- Extend existing protocol, forwarding, transform, and core runtime
tests.
This commit is contained in:
Adam Perry @ OpenAI
2026-06-12 11:38:01 -07:00
committed by GitHub
parent 84520225b9
commit 52a50aec70
40 changed files with 546 additions and 228 deletions
@@ -312,5 +312,5 @@ fn single_local_environment_cwd(turn: &TurnContext) -> Result<&AbsolutePathBuf,
));
}
Ok(&turn_environment.cwd)
Ok(turn_environment.cwd())
}
@@ -52,6 +52,7 @@ use codex_sandboxing::policy_transforms::normalize_additional_permissions;
use codex_tools::ToolName;
use codex_tools::ToolSpec;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
const APPLY_PATCH_ARGUMENT_DIFF_BUFFER_INTERVAL: Duration = Duration::from_millis(500);
/// Handles freeform `apply_patch` requests and routes verified patches to the
@@ -358,9 +359,12 @@ impl ApplyPatchHandler {
"apply_patch is unavailable in this session".to_string(),
));
};
let cwd = turn_environment.cwd.clone();
let cwd = turn_environment.cwd().clone();
let fs = turn_environment.environment.get_filesystem();
let sandbox = turn.file_system_sandbox_context(/*additional_permissions*/ None, &cwd);
let sandbox = turn.file_system_sandbox_context(
/*additional_permissions*/ None,
turn_environment.cwd_uri(),
);
match codex_apply_patch::verify_apply_patch_args(args, &cwd, fs.as_ref(), Some(&sandbox))
.await
{
@@ -522,7 +526,14 @@ pub(crate) async fn intercept_apply_patch(
call_id: &str,
tool_name: &str,
) -> Result<Option<FunctionToolOutput>, FunctionCallError> {
let sandbox = turn.file_system_sandbox_context(/*additional_permissions*/ None, cwd);
let sandbox_cwd = PathUri::from_abs_path(cwd).map_err(|_| {
FunctionCallError::RespondToModel(
"unable to prepare filesystem sandbox: working directory cannot be represented as a file URI"
.to_string(),
)
})?;
let sandbox =
turn.file_system_sandbox_context(/*additional_permissions*/ None, &sandbox_cwd);
match codex_apply_patch::maybe_parse_apply_patch_verified(command, cwd, fs, Some(&sandbox))
.await
{
@@ -117,19 +117,20 @@ async fn to_extension_call(invocation: &ToolInvocation) -> ExtensionToolCall {
let additional_permissions = apply_granted_turn_permissions(
invocation.session.as_ref(),
&environment.environment_id,
environment.cwd.as_path(),
environment.cwd().as_path(),
SandboxPermissions::UseDefault,
/*additional_permissions*/ None,
)
.await
.additional_permissions;
let file_system_sandbox_context = invocation
.turn
.file_system_sandbox_context(additional_permissions, environment.cwd_uri());
environments.push(ToolEnvironment {
environment_id: environment.environment_id.clone(),
cwd: environment.cwd.clone(),
cwd: environment.cwd().clone(),
file_system: environment.environment.get_filesystem(),
file_system_sandbox_context: invocation
.turn
.file_system_sandbox_context(additional_permissions, &environment.cwd),
file_system_sandbox_context,
});
}
ExtensionToolCall {
@@ -310,6 +311,12 @@ mod tests {
let turn_id = turn.sub_id.clone();
let model = turn.model_info.slug.clone();
let truncation_policy = turn.truncation_policy;
let expected_sandbox_cwds = turn
.environments
.turn_environments
.iter()
.map(|environment| Some(environment.cwd_uri().clone()))
.collect::<Vec<_>>();
let history_item = ResponseItem::Message {
id: None,
role: "user".to_string(),
@@ -354,6 +361,14 @@ mod tests {
);
assert_eq!(captured_call.model, model);
assert_eq!(captured_call.truncation_policy, truncation_policy);
assert_eq!(
captured_call
.environments
.iter()
.map(|environment| environment.file_system_sandbox_context.cwd.clone())
.collect::<Vec<_>>(),
expected_sandbox_cwds
);
assert_eq!(
captured_call.conversation_history.items(),
std::slice::from_ref(&history_item)
@@ -71,7 +71,7 @@ impl RequestPermissionsHandler {
));
};
let mut args: RequestPermissionsArgs =
parse_arguments_with_base_path(&arguments, &turn_environment.cwd)?;
parse_arguments_with_base_path(&arguments, turn_environment.cwd())?;
args.permissions = normalize_additional_permissions(args.permissions.into())
.map(codex_protocol::request_permissions::RequestPermissionProfile::from)
.map_err(FunctionCallError::RespondToModel)?;
@@ -134,8 +134,8 @@ impl ExecCommandHandler {
.as_deref()
.filter(|workdir| !workdir.is_empty())
.map_or_else(
|| turn_environment.cwd.clone(),
|workdir| turn_environment.cwd.join(workdir),
|| turn_environment.cwd().clone(),
|workdir| turn_environment.cwd().join(workdir),
);
let environment = Arc::clone(&turn_environment.environment);
let fs = environment.get_filesystem();
@@ -270,7 +270,7 @@ impl ExecCommandHandler {
yield_time_ms,
max_output_tokens,
cwd,
sandbox_cwd: turn_environment.cwd.clone(),
sandbox_cwd: turn_environment.cwd().clone(),
environment,
shell_mode,
network: context.turn.network.clone(),
+25 -12
View File
@@ -143,9 +143,12 @@ impl ViewImageHandler {
"view_image is unavailable in this session".to_string(),
));
};
let cwd = turn_environment.cwd.clone();
let cwd = turn_environment.cwd().clone();
let abs_path = cwd.join(path);
let sandbox = turn.file_system_sandbox_context(/*additional_permissions*/ None, &cwd);
let sandbox = turn.file_system_sandbox_context(
/*additional_permissions*/ None,
turn_environment.cwd_uri(),
);
let fs = turn_environment.environment.get_filesystem();
let path_uri = PathUri::from_abs_path(&abs_path).map_err(|error| {
FunctionCallError::RespondToModel(format!(
@@ -268,16 +271,34 @@ impl ToolOutput for ViewImageOutput {
mod tests {
use super::*;
use crate::session::tests::make_session_and_context;
use crate::session::turn_context::TurnEnvironment;
use crate::tools::context::ToolCallSource;
use crate::tools::context::ToolInvocation;
use crate::turn_diff_tracker::TurnDiffTracker;
use codex_protocol::models::PermissionProfile;
use codex_utils_absolute_path::AbsolutePathBuf;
use core_test_support::TempDirExt;
use pretty_assertions::assert_eq;
use serde_json::json;
use std::sync::Arc;
use tokio::sync::Mutex;
fn replace_primary_environment_cwd(turn: &mut crate::TurnContext, cwd: AbsolutePathBuf) {
let current = turn
.environments
.turn_environments
.first()
.cloned()
.expect("default local turn environment");
turn.environments.turn_environments[0] = TurnEnvironment::new(
current.environment_id,
current.environment,
cwd,
current.shell,
)
.expect("image cwd URI");
}
#[test]
fn log_preview_omits_image_data() {
let output = ViewImageOutput {
@@ -314,11 +335,7 @@ mod tests {
let image_dir = tempfile::tempdir().expect("create image temp dir");
let image_cwd = image_dir.abs();
turn.environments
.turn_environments
.first_mut()
.expect("default local turn environment")
.cwd = image_cwd.clone();
replace_primary_environment_cwd(&mut turn, image_cwd.clone());
let image_path = image_cwd.join("image.png");
std::fs::write(image_path.as_path(), b"not a real image").expect("write test image");
turn.permission_profile = PermissionProfile::read_only();
@@ -381,11 +398,7 @@ mod tests {
let image_dir = tempfile::tempdir().expect("create image temp dir");
let image_cwd = image_dir.abs();
turn.environments
.turn_environments
.first_mut()
.expect("default local turn environment")
.cwd = image_cwd.clone();
replace_primary_environment_cwd(&mut turn, image_cwd.clone());
let image_path = image_cwd.join("image.png");
std::fs::write(image_path.as_path(), b"not a real image").expect("write test image");
turn.permission_profile = PermissionProfile::Disabled;
+8 -2
View File
@@ -39,6 +39,7 @@ use codex_protocol::protocol::NetworkPolicyRuleAction;
use codex_protocol::protocol::ReviewDecision;
use codex_sandboxing::SandboxManager;
use codex_sandboxing::SandboxType;
use codex_utils_path_uri::PathUri;
use std::time::Instant;
pub(crate) struct ToolOrchestrator {
@@ -239,13 +240,18 @@ impl ToolOrchestrator {
let use_legacy_landlock = turn_ctx.features.use_legacy_landlock();
#[allow(deprecated)]
let sandbox_cwd = tool.sandbox_cwd(req).unwrap_or(&turn_ctx.cwd);
let sandbox_policy_cwd = PathUri::from_abs_path(sandbox_cwd).map_err(|_| {
ToolError::Codex(CodexErr::InvalidRequest(
"sandbox policy cwd cannot be represented as a file URI".to_string(),
))
})?;
let workspace_roots = turn_ctx.config.effective_workspace_roots();
let initial_attempt = SandboxAttempt {
sandbox: initial_sandbox,
permissions: &turn_ctx.permission_profile,
enforce_managed_network: managed_network_active,
manager: &self.sandbox,
sandbox_cwd,
sandbox_cwd: &sandbox_policy_cwd,
workspace_roots: workspace_roots.as_slice(),
codex_linux_sandbox_exe: turn_ctx.codex_linux_sandbox_exe.as_ref(),
use_legacy_landlock,
@@ -418,7 +424,7 @@ impl ToolOrchestrator {
permissions: &turn_ctx.permission_profile,
enforce_managed_network: managed_network_active,
manager: &self.sandbox,
sandbox_cwd,
sandbox_cwd: &sandbox_policy_cwd,
workspace_roots: workspace_roots.as_slice(),
codex_linux_sandbox_exe: retry_codex_linux_sandbox_exe,
use_legacy_landlock,
@@ -11,16 +11,18 @@ use codex_sandboxing::SandboxManager;
use codex_sandboxing::SandboxType;
use codex_sandboxing::policy_transforms::effective_file_system_sandbox_policy;
use codex_sandboxing::policy_transforms::effective_network_sandbox_policy;
use codex_utils_path_uri::PathUri;
use core_test_support::PathBufExt;
use pretty_assertions::assert_eq;
use std::collections::HashMap;
fn test_turn_environment(environment_id: &str) -> crate::session::turn_context::TurnEnvironment {
crate::session::turn_context::TurnEnvironment {
environment_id: environment_id.to_string(),
environment: std::sync::Arc::new(codex_exec_server::Environment::default_for_tests()),
cwd: std::env::temp_dir().abs(),
shell: None,
}
crate::session::turn_context::TurnEnvironment::new(
environment_id.to_string(),
std::sync::Arc::new(codex_exec_server::Environment::default_for_tests()),
std::env::temp_dir().abs(),
/*shell*/ None,
)
.expect("turn environment")
}
#[test]
@@ -206,12 +208,13 @@ async fn file_system_sandbox_context_uses_active_attempt() {
NetworkSandboxPolicy::Restricted,
);
let manager = SandboxManager::new();
let sandbox_policy_cwd = PathUri::from_abs_path(&path).expect("path URI");
let attempt = SandboxAttempt {
sandbox: SandboxType::MacosSeatbelt,
permissions: &permissions,
enforce_managed_network: false,
manager: &manager,
sandbox_cwd: &path,
sandbox_cwd: &sandbox_policy_cwd,
workspace_roots: std::slice::from_ref(&path),
codex_linux_sandbox_exe: None,
use_legacy_landlock: true,
@@ -232,7 +235,10 @@ async fn file_system_sandbox_context_uses_active_attempt() {
let expected_permissions =
PermissionProfile::from_runtime_permissions(&file_system_policy, network_policy);
assert_eq!(sandbox.permissions, expected_permissions);
assert_eq!(sandbox.cwd, Some(path.clone()));
assert_eq!(
sandbox.cwd,
Some(codex_utils_path_uri::PathUri::from_abs_path(&path).expect("path URI"))
);
assert_eq!(
sandbox.windows_sandbox_level,
WindowsSandboxLevel::RestrictedToken
@@ -260,12 +266,13 @@ async fn no_sandbox_attempt_has_no_file_system_context() {
};
let permissions = PermissionProfile::Disabled;
let manager = SandboxManager::new();
let sandbox_policy_cwd = PathUri::from_abs_path(&path).expect("path URI");
let attempt = SandboxAttempt {
sandbox: SandboxType::None,
permissions: &permissions,
enforce_managed_network: false,
manager: &manager,
sandbox_cwd: &path,
sandbox_cwd: &sandbox_policy_cwd,
workspace_roots: std::slice::from_ref(&path),
codex_linux_sandbox_exe: None,
use_legacy_landlock: false,
+11 -3
View File
@@ -21,10 +21,12 @@ use codex_network_proxy::PROXY_ENV_KEYS;
use codex_network_proxy::PROXY_GIT_SSH_COMMAND_ENV_KEY;
use codex_network_proxy::is_managed_mitm_ca_trust_bundle_path;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::error::CodexErr;
use codex_protocol::models::AdditionalPermissionProfile;
use codex_sandboxing::SandboxCommand;
use codex_sandboxing::SandboxType;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
use std::collections::HashMap;
#[cfg(unix)]
use std::path::Path;
@@ -33,8 +35,9 @@ pub(crate) mod apply_patch;
pub(crate) mod shell;
pub(crate) mod unified_exec;
/// Shared helper to construct sandbox transform inputs from a tokenized command line.
/// Validates that at least a program is present.
/// Shared helper to construct sandbox transform inputs from a tokenized command line and native
/// working directory. Validates that at least a program is present and that the working directory
/// has a file URI representation.
pub(crate) fn build_sandbox_command(
command: &[String],
cwd: &AbsolutePathBuf,
@@ -44,10 +47,15 @@ pub(crate) fn build_sandbox_command(
let (program, args) = command
.split_first()
.ok_or_else(|| ToolError::Rejected("command args are empty".to_string()))?;
let cwd = PathUri::from_abs_path(cwd).map_err(|_| {
ToolError::Codex(CodexErr::InvalidRequest(
"command cwd cannot be represented as a file URI".to_string(),
))
})?;
Ok(SandboxCommand {
program: program.clone().into(),
args: args.to_vec(),
cwd: cwd.clone(),
cwd,
env: env.clone(),
additional_permissions,
})
+13 -4
View File
@@ -25,6 +25,7 @@ use codex_protocol::models::PermissionProfile;
use codex_sandboxing::SandboxManager;
use codex_sandboxing::SandboxType;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
use core_test_support::PathBufExt;
use core_test_support::PathExt;
use pretty_assertions::assert_eq;
@@ -88,18 +89,21 @@ async fn test_network_proxy() -> anyhow::Result<NetworkProxy> {
async fn explicit_escalation_prepares_exec_without_managed_network() -> anyhow::Result<()> {
let proxy = test_network_proxy().await?;
let dir = tempdir().expect("create temp dir");
let cwd = dir.path().abs();
let command_cwd = dir.path().join("command").abs();
let native_sandbox_policy_cwd = dir.path().join("sandbox-policy").abs();
let mut env = HashMap::from([("CUSTOM_ENV".to_string(), "kept".to_string())]);
proxy.apply_to_env(&mut env);
let command = vec!["/bin/echo".to_string(), "ok".to_string()];
let command = build_sandbox_command(
&command,
&cwd,
&command_cwd,
&exec_env_for_sandbox_permissions(&env, SandboxPermissions::RequireEscalated),
/*additional_permissions*/ None,
)
.expect("build sandbox command");
assert_eq!(command.cwd, PathUri::from_abs_path(&command_cwd)?);
let sandbox_policy_cwd = PathUri::from_abs_path(&native_sandbox_policy_cwd)?;
let options = ExecOptions {
expiration: ExecExpiration::DefaultTimeout,
capture_policy: ExecCapturePolicy::ShellTool,
@@ -111,8 +115,8 @@ async fn explicit_escalation_prepares_exec_without_managed_network() -> anyhow::
permissions: &permissions,
enforce_managed_network: false,
manager: &manager,
sandbox_cwd: &cwd,
workspace_roots: std::slice::from_ref(&cwd),
sandbox_cwd: &sandbox_policy_cwd,
workspace_roots: std::slice::from_ref(&native_sandbox_policy_cwd),
codex_linux_sandbox_exe: None,
use_legacy_landlock: false,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
@@ -131,6 +135,11 @@ async fn explicit_escalation_prepares_exec_without_managed_network() -> anyhow::
)
.expect("prepare exec request");
assert_eq!(exec_request.cwd, command_cwd);
assert_eq!(
exec_request.windows_sandbox_policy_cwd,
native_sandbox_policy_cwd
);
assert_eq!(exec_request.network, None);
for key in PROXY_ENV_KEYS {
assert_eq!(exec_request.env.get(*key), None, "{key} should be unset");
+1 -1
View File
@@ -310,7 +310,7 @@ impl ToolRuntime<ShellRequest, ExecToolCallOutput> for ShellRuntime {
};
let env = attempt
.env_for(command, options, managed_network)
.map_err(|err| ToolError::Codex(err.into()))?;
.map_err(ToolError::Codex)?;
let out = execute_env(env, Self::stdout_stream(ctx))
.await
.map_err(ToolError::Codex)?;
@@ -66,6 +66,7 @@ use codex_shell_escalation::ShellCommandExecutor;
use codex_shell_escalation::ShellCommandExecutorFuture;
use codex_shell_escalation::Stopwatch;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
@@ -143,7 +144,7 @@ pub(super) async fn try_run_zsh_fork(
options,
managed_network_for_sandbox_permissions(req.network.as_ref(), req.sandbox_permissions),
)
.map_err(|err| ToolError::Codex(err.into()))?;
.map_err(ToolError::Codex)?;
let crate::sandboxing::ExecRequest {
command,
cwd: sandbox_cwd,
@@ -968,10 +969,19 @@ impl CoreShellCommandExecutor {
self.windows_sandbox_level,
self.network.is_some(),
);
let cwd = PathUri::from_abs_path(workdir).map_err(|_| {
CodexErr::InvalidRequest("command cwd cannot be represented as a file URI".to_string())
})?;
let sandbox_policy_cwd =
PathUri::from_abs_path(&self.sandbox_policy_cwd).map_err(|_| {
CodexErr::InvalidRequest(
"sandbox policy cwd cannot be represented as a file URI".to_string(),
)
})?;
let command = SandboxCommand {
program: program.clone().into(),
args: args.to_vec(),
cwd: workdir.clone(),
cwd,
env,
additional_permissions,
};
@@ -985,7 +995,7 @@ impl CoreShellCommandExecutor {
sandbox,
enforce_managed_network: self.network.is_some(),
network: self.network.as_ref(),
sandbox_policy_cwd: &self.sandbox_policy_cwd,
sandbox_policy_cwd: &sandbox_policy_cwd,
codex_linux_sandbox_exe: self.codex_linux_sandbox_exe.as_deref(),
use_legacy_landlock: self.use_legacy_landlock,
windows_sandbox_level: self.windows_sandbox_level,
@@ -994,7 +1004,6 @@ impl CoreShellCommandExecutor {
let mut exec_request = crate::sandboxing::ExecRequest::from_sandbox_exec_request(
exec_request,
options,
self.sandbox_policy_cwd.clone(),
self.windows_sandbox_workspace_roots.clone(),
);
if let Some(network) = exec_request.network.as_ref() {
@@ -326,11 +326,16 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
if let UnifiedExecShellMode::ZshFork(zsh_fork_config) = &self.shell_mode {
let command =
build_sandbox_command(&command, &req.cwd, &env, req.additional_permissions.clone())
.map_err(|_| ToolError::Rejected("missing command line for PTY".to_string()))?;
.map_err(|error| match error {
ToolError::Rejected(_) => {
ToolError::Rejected("missing command line for PTY".to_string())
}
error @ ToolError::Codex(_) => error,
})?;
let options = unified_exec_options(attempt.network_denial_cancellation_token.clone());
let mut exec_env = attempt
.env_for(command, options, managed_network)
.map_err(|err| ToolError::Codex(err.into()))?;
.map_err(ToolError::Codex)?;
exec_env.exec_server_env_config = req.exec_server_env_config.clone();
match zsh_fork_backend::maybe_prepare_unified_exec(
req,
@@ -377,11 +382,16 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
}
let command =
build_sandbox_command(&command, &req.cwd, &env, req.additional_permissions.clone())
.map_err(|_| ToolError::Rejected("missing command line for PTY".to_string()))?;
.map_err(|error| match error {
ToolError::Rejected(_) => {
ToolError::Rejected("missing command line for PTY".to_string())
}
error @ ToolError::Codex(_) => error,
})?;
let options = unified_exec_options(attempt.network_denial_cancellation_token.clone());
let mut exec_env = attempt
.env_for(command, options, managed_network)
.map_err(|err| ToolError::Codex(err.into()))?;
.map_err(ToolError::Codex)?;
exec_env.exec_server_env_config = req.exec_server_env_config.clone();
self.manager
.open_session_with_exec_env(
+11 -17
View File
@@ -21,12 +21,12 @@ use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::ReviewDecision;
use codex_sandboxing::SandboxCommand;
use codex_sandboxing::SandboxManager;
use codex_sandboxing::SandboxTransformError;
use codex_sandboxing::SandboxTransformRequest;
use codex_sandboxing::SandboxType;
use codex_sandboxing::SandboxablePreference;
use codex_tools::ToolName;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
use futures::Future;
use futures::future::BoxFuture;
use serde::Serialize;
@@ -411,7 +411,7 @@ pub(crate) struct SandboxAttempt<'a> {
pub permissions: &'a codex_protocol::models::PermissionProfile,
pub enforce_managed_network: bool,
pub(crate) manager: &'a SandboxManager,
pub(crate) sandbox_cwd: &'a AbsolutePathBuf,
pub(crate) sandbox_cwd: &'a PathUri,
pub(crate) workspace_roots: &'a [AbsolutePathBuf],
pub codex_linux_sandbox_exe: Option<&'a std::path::PathBuf>,
pub use_legacy_landlock: bool,
@@ -426,8 +426,9 @@ impl<'a> SandboxAttempt<'a> {
command: SandboxCommand,
options: ExecOptions,
network: Option<&NetworkProxy>,
) -> Result<crate::sandboxing::ExecRequest, SandboxTransformError> {
self.manager
) -> Result<crate::sandboxing::ExecRequest, CodexErr> {
let request = self
.manager
.transform(SandboxTransformRequest {
command,
permissions: self.permissions,
@@ -442,19 +443,12 @@ impl<'a> SandboxAttempt<'a> {
windows_sandbox_level: self.windows_sandbox_level,
windows_sandbox_private_desktop: self.windows_sandbox_private_desktop,
})
.map(|request| {
let windows_sandbox_policy_cwd =
codex_utils_absolute_path::AbsolutePathBuf::try_from(
self.sandbox_cwd.to_path_buf(),
)
.unwrap_or_else(|_| request.cwd.clone());
crate::sandboxing::ExecRequest::from_sandbox_exec_request(
request,
options,
windows_sandbox_policy_cwd,
self.workspace_roots.to_vec(),
)
})
.map_err(CodexErr::from)?;
Ok(crate::sandboxing::ExecRequest::from_sandbox_exec_request(
request,
options,
self.workspace_roots.to_vec(),
))
}
}
+10 -9
View File
@@ -573,21 +573,22 @@ async fn zsh_fork_unified_exec_keeps_shell_parameter_when_remote_environment_ava
.environments
.primary()
.expect("primary environment")
.cwd
.cwd()
.clone();
turn.environments
.turn_environments
.push(crate::session::turn_context::TurnEnvironment {
environment_id: "remote".to_string(),
environment: Arc::new(
turn.environments.turn_environments.push(
crate::session::turn_context::TurnEnvironment::new(
"remote".to_string(),
Arc::new(
codex_exec_server::Environment::create_for_tests(Some(
"ws://127.0.0.1:1/remote-exec-server".to_string(),
))
.expect("remote test environment"),
),
cwd: remote_cwd,
shell: None,
});
remote_cwd,
/*shell*/ None,
)
.expect("turn environment"),
);
})
.await;