Use AbsolutePathBuf for exec cwd plumbing (#17063)

## Summary
- Carry `AbsolutePathBuf` through tool cwd parsing/resolution instead of
resolving workdirs to raw `PathBuf`s.
- Type exec/sandbox request cwd fields as `AbsolutePathBuf` through
`ExecParams`, `ExecRequest`, `SandboxCommand`, and unified exec runtime
requests.
- Keep `PathBuf` conversions at external/event boundaries and update
existing tests/fixtures for the typed cwd.

## Validation
- `cargo check -p codex-core --tests`
- `cargo check -p codex-sandboxing --tests`
- `cargo test -p codex-sandboxing`
- `cargo test -p codex-core --lib tools::handlers::`
- `just fix -p codex-sandboxing`
- `just fix -p codex-core`
- `just fmt`

Full `codex-core` test suite was not run locally; per repo guidance I
kept local validation targeted.
This commit is contained in:
pakrym-oai
2026-04-08 10:54:12 -07:00
committed by GitHub
Unverified
parent d90a348870
commit 35b5720e8d
31 changed files with 119 additions and 126 deletions
@@ -1829,7 +1829,7 @@ impl CodexMessageProcessor {
return;
}
let cwd = cwd.unwrap_or_else(|| self.config.cwd.to_path_buf());
let cwd = cwd.map_or_else(|| self.config.cwd.clone(), |cwd| self.config.cwd.join(cwd));
let mut env = create_env(
&self.config.permissions.shell_environment_policy,
/*thread_id*/ None,
+3 -3
View File
@@ -708,13 +708,13 @@ fn internal_error(message: String) -> JSONRPCErrorError {
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use std::path::PathBuf;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::ReadOnlyAccess;
use codex_protocol::protocol::SandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
#[cfg(not(target_os = "windows"))]
use tokio::time::Duration;
@@ -736,7 +736,7 @@ mod tests {
};
ExecRequest::new(
vec!["cmd".to_string()],
PathBuf::from("."),
AbsolutePathBuf::current_dir().expect("current dir"),
HashMap::new(),
/*network*/ None,
ExecExpiration::DefaultTimeout,
@@ -848,7 +848,7 @@ mod tests {
process_id: Some("proc-100".to_string()),
exec_request: ExecRequest::new(
vec!["sh".to_string(), "-lc".to_string(), "sleep 30".to_string()],
PathBuf::from("."),
AbsolutePathBuf::current_dir().expect("current dir"),
HashMap::new(),
/*network*/ None,
ExecExpiration::Cancellation(CancellationToken::new()),
+2 -3
View File
@@ -1019,10 +1019,9 @@ impl TurnContext {
}
}
pub(crate) fn resolve_path(&self, path: Option<String>) -> PathBuf {
pub(crate) fn resolve_path(&self, path: Option<String>) -> AbsolutePathBuf {
path.as_ref()
.map(PathBuf::from)
.map_or_else(|| self.cwd.to_path_buf(), |p| self.cwd.as_path().join(p))
.map_or_else(|| self.cwd.clone(), |path| self.cwd.join(path))
}
pub(crate) fn compact_prompt(&self) -> &str {
+1 -1
View File
@@ -5539,7 +5539,7 @@ async fn rejects_escalated_permissions_when_policy_not_on_request() {
"echo hi".to_string(),
]
},
cwd: turn_context.cwd.to_path_buf(),
cwd: turn_context.cwd.clone(),
expiration: timeout_ms.into(),
capture_policy: ExecCapturePolicy::ShellTool,
env: HashMap::new(),
+1 -1
View File
@@ -125,7 +125,7 @@ async fn guardian_allows_shell_additional_permissions_requests_past_policy_valid
"echo hi".to_string(),
]
},
cwd: turn_context.cwd.to_path_buf(),
cwd: turn_context.cwd.clone(),
expiration: expiration_ms.into(),
capture_policy: ExecCapturePolicy::ShellTool,
env: HashMap::new(),
+3 -3
View File
@@ -82,7 +82,7 @@ pub const IO_DRAIN_TIMEOUT_MS: u64 = 2_000; // 2 s should be plenty for local pi
#[derive(Debug)]
pub struct ExecParams {
pub command: Vec<String>,
pub cwd: PathBuf,
pub cwd: AbsolutePathBuf,
pub expiration: ExecExpiration,
pub capture_policy: ExecCapturePolicy,
pub env: HashMap<String, String>,
@@ -293,7 +293,7 @@ pub fn build_exec_request(
enforce_managed_network,
network: network.as_ref(),
sandbox_policy_cwd: sandbox_cwd,
codex_linux_sandbox_exe: codex_linux_sandbox_exe.as_ref(),
codex_linux_sandbox_exe: codex_linux_sandbox_exe.as_deref(),
use_legacy_landlock,
windows_sandbox_level,
windows_sandbox_private_desktop,
@@ -804,7 +804,7 @@ async fn exec(
program: PathBuf::from(program),
args: args.into(),
arg0: arg0_ref,
cwd,
cwd: cwd.to_path_buf(),
network_sandbox_policy,
// The environment already has attempt-scoped proxy settings from
// apply_to_env_for_attempt above. Passing network here would reapply
+5 -5
View File
@@ -264,7 +264,7 @@ async fn exec_full_buffer_capture_ignores_expiration() -> Result<()> {
let output = exec(
ExecParams {
command,
cwd: std::env::current_dir()?,
cwd: codex_utils_absolute_path::AbsolutePathBuf::current_dir()?,
expiration: 1.into(),
capture_policy: ExecCapturePolicy::FullBuffer,
env,
@@ -304,7 +304,7 @@ async fn exec_full_buffer_capture_keeps_io_drain_timeout_when_descendant_holds_p
"-c".to_string(),
"printf hello; sleep 30 &".to_string(),
],
cwd: std::env::current_dir()?,
cwd: codex_utils_absolute_path::AbsolutePathBuf::current_dir()?,
expiration: 1.into(),
capture_policy: ExecCapturePolicy::FullBuffer,
env: std::env::vars().collect(),
@@ -350,7 +350,7 @@ async fn process_exec_tool_call_preserves_full_buffer_capture_policy() -> Result
format!("sleep 0.05; head -c {byte_count} /dev/zero | tr '\\0' 'a'"),
];
let cwd = std::env::current_dir()?;
let cwd = codex_utils_absolute_path::AbsolutePathBuf::current_dir()?;
let sandbox_policy = SandboxPolicy::DangerFullAccess;
let output = process_exec_tool_call(
ExecParams {
@@ -723,7 +723,7 @@ async fn kill_child_process_group_kills_grandchildren_on_timeout() -> Result<()>
"-c".to_string(),
"sleep 60 & echo $!; sleep 60".to_string(),
];
let cwd = std::env::current_dir()?;
let cwd = codex_utils_absolute_path::AbsolutePathBuf::current_dir()?;
let env: HashMap<String, String> = std::env::vars().collect();
let params = ExecParams {
command,
@@ -780,7 +780,7 @@ async fn kill_child_process_group_kills_grandchildren_on_timeout() -> Result<()>
#[tokio::test]
async fn process_exec_tool_call_respects_cancellation_token() -> Result<()> {
let command = long_running_command();
let cwd = std::env::current_dir()?;
let cwd = codex_utils_absolute_path::AbsolutePathBuf::current_dir()?;
let env: HashMap<String, String> = std::env::vars().collect();
let cancel_token = CancellationToken::new();
let cancel_tx = cancel_token.clone();
+18 -4
View File
@@ -80,12 +80,20 @@ fn shell_command_for_invocation(invocation: &ToolInvocation) -> Option<(Vec<Stri
match invocation.tool_name.as_str() {
"shell" => serde_json::from_str::<ShellToolCallParams>(arguments)
.ok()
.map(|params| (params.command, invocation.turn.resolve_path(params.workdir))),
.map(|params| {
(
params.command,
invocation.turn.resolve_path(params.workdir).to_path_buf(),
)
}),
"shell_command" => serde_json::from_str::<ShellCommandToolCallParams>(arguments)
.ok()
.map(|params| {
if !invocation.turn.tools_config.allow_login_shell && params.login == Some(true) {
return (Vec::new(), invocation.turn.resolve_path(params.workdir));
return (
Vec::new(),
invocation.turn.resolve_path(params.workdir).to_path_buf(),
);
}
let use_login_shell = params
.login
@@ -94,7 +102,10 @@ fn shell_command_for_invocation(invocation: &ToolInvocation) -> Option<(Vec<Stri
.session
.user_shell()
.derive_exec_args(&params.command, use_login_shell);
(command, invocation.turn.resolve_path(params.workdir))
(
command,
invocation.turn.resolve_path(params.workdir).to_path_buf(),
)
}),
"exec_command" => serde_json::from_str::<ExecCommandArgs>(arguments)
.ok()
@@ -106,7 +117,10 @@ fn shell_command_for_invocation(invocation: &ToolInvocation) -> Option<(Vec<Stri
invocation.turn.tools_config.allow_login_shell,
)
.ok()?;
Some((command, invocation.turn.resolve_path(params.workdir)))
Some((
command,
invocation.turn.resolve_path(params.workdir).to_path_buf(),
))
}),
_ => None,
}
+3 -3
View File
@@ -24,8 +24,8 @@ use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::SandboxPolicy;
use codex_sandboxing::SandboxExecRequest;
use codex_sandboxing::SandboxType;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug)]
pub(crate) struct ExecOptions {
@@ -36,7 +36,7 @@ pub(crate) struct ExecOptions {
#[derive(Debug)]
pub struct ExecRequest {
pub command: Vec<String>,
pub cwd: PathBuf,
pub cwd: AbsolutePathBuf,
pub env: HashMap<String, String>,
pub network: Option<NetworkProxy>,
pub expiration: ExecExpiration,
@@ -56,7 +56,7 @@ impl ExecRequest {
#[allow(clippy::too_many_arguments)]
pub fn new(
command: Vec<String>,
cwd: PathBuf,
cwd: AbsolutePathBuf,
env: HashMap<String, String>,
network: Option<NetworkProxy>,
expiration: ExecExpiration,
+1 -1
View File
@@ -160,7 +160,7 @@ pub(crate) async fn execute_user_shell_command(
let sandbox_policy = SandboxPolicy::DangerFullAccess;
let exec_env = ExecRequest {
command: exec_command.clone(),
cwd: cwd.to_path_buf(),
cwd: cwd.clone(),
env: exec_env_map,
network: turn_context.network.clone(),
// TODO(zhao-oai): Now that we have ExecExpiration::Cancellation, we
@@ -18,6 +18,7 @@ use codex_protocol::protocol::AgentStatus;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::SubAgentSource;
use codex_protocol::user_input::UserInput;
use codex_utils_absolute_path::AbsolutePathBuf;
use futures::StreamExt;
use futures::stream::FuturesUnordered;
use serde::Deserialize;
@@ -25,7 +26,6 @@ use serde::Serialize;
use serde_json::Value;
use std::collections::HashMap;
use std::collections::HashSet;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::watch::Receiver;
@@ -309,7 +309,7 @@ mod spawn_agents_on_csv {
let job_id = Uuid::new_v4().to_string();
let output_csv_path = args.output_csv_path.map_or_else(
|| default_output_csv_path(input_path.as_path(), job_id.as_str()),
|| default_output_csv_path(&input_path, job_id.as_str()),
|path| turn.resolve_path(Some(path)),
);
let job_suffix = &job_id[..8];
@@ -1089,13 +1089,17 @@ fn is_item_stale(item: &codex_state::AgentJobItem, runtime_timeout: Duration) ->
}
}
fn default_output_csv_path(input_csv_path: &Path, job_id: &str) -> PathBuf {
fn default_output_csv_path(input_csv_path: &AbsolutePathBuf, job_id: &str) -> AbsolutePathBuf {
let stem = input_csv_path
.as_path()
.file_stem()
.and_then(|stem| stem.to_str())
.unwrap_or("agent_job_output");
let job_suffix = &job_id[..8];
input_csv_path.with_file_name(format!("{stem}.agent-job-{job_suffix}.csv"))
let output_dir = input_csv_path
.parent()
.unwrap_or_else(|| input_csv_path.clone());
output_dir.join(format!("{stem}.agent-job-{job_suffix}.csv"))
}
fn parse_csv(content: &str) -> Result<(Vec<String>, Vec<Vec<String>>), String> {
+5 -9
View File
@@ -21,11 +21,11 @@ mod view_image;
use codex_sandboxing::policy_transforms::intersect_permission_profiles;
use codex_sandboxing::policy_transforms::merge_permission_profiles;
use codex_sandboxing::policy_transforms::normalize_additional_permissions;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_absolute_path::AbsolutePathBufGuard;
use serde::Deserialize;
use serde_json::Value;
use std::path::Path;
use std::path::PathBuf;
use crate::codex::Session;
use crate::function_tool::FunctionCallError;
@@ -63,7 +63,7 @@ where
fn parse_arguments_with_base_path<T>(
arguments: &str,
base_path: &Path,
base_path: &AbsolutePathBuf,
) -> Result<T, FunctionCallError>
where
T: for<'de> Deserialize<'de>,
@@ -74,18 +74,14 @@ where
fn resolve_workdir_base_path(
arguments: &str,
default_cwd: &Path,
) -> Result<PathBuf, FunctionCallError> {
default_cwd: &AbsolutePathBuf,
) -> Result<AbsolutePathBuf, FunctionCallError> {
let arguments: Value = parse_arguments(arguments)?;
Ok(arguments
.get("workdir")
.and_then(Value::as_str)
.filter(|workdir| !workdir.is_empty())
.map(PathBuf::from)
.map_or_else(
|| default_cwd.to_path_buf(),
|workdir| crate::util::resolve_path(default_cwd, &workdir),
))
.map_or_else(|| default_cwd.clone(), |workdir| default_cwd.join(workdir)))
}
/// Validates feature/policy constraints for `with_additional_permissions` and
@@ -37,7 +37,7 @@ impl ToolHandler for RequestPermissionsHandler {
};
let mut args: RequestPermissionsArgs =
parse_arguments_with_base_path(&arguments, turn.cwd.as_path())?;
parse_arguments_with_base_path(&arguments, &turn.cwd)?;
args.permissions = normalize_additional_permissions(args.permissions.into())
.map(codex_protocol::request_permissions::RequestPermissionProfile::from)
.map_err(FunctionCallError::RespondToModel)?;
+6 -15
View File
@@ -39,7 +39,6 @@ use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::ExecCommandSource;
use codex_shell_command::is_safe_command::is_known_safe_command;
use codex_tools::ShellCommandBackendConfig;
use codex_utils_absolute_path::AbsolutePathBuf;
pub struct ShellHandler;
@@ -234,9 +233,8 @@ impl ToolHandler for ShellHandler {
match payload {
ToolPayload::Function { arguments } => {
let cwd = resolve_workdir_base_path(&arguments, turn.cwd.as_path())?;
let params: ShellToolCallParams =
parse_arguments_with_base_path(&arguments, cwd.as_path())?;
let cwd = resolve_workdir_base_path(&arguments, &turn.cwd)?;
let params: ShellToolCallParams = parse_arguments_with_base_path(&arguments, &cwd)?;
let prefix_rule = params.prefix_rule.clone();
let exec_params =
Self::to_exec_params(&params, turn.as_ref(), session.conversation_id);
@@ -345,9 +343,8 @@ impl ToolHandler for ShellCommandHandler {
)));
};
let cwd = resolve_workdir_base_path(&arguments, turn.cwd.as_path())?;
let params: ShellCommandToolCallParams =
parse_arguments_with_base_path(&arguments, cwd.as_path())?;
let cwd = resolve_workdir_base_path(&arguments, &turn.cwd)?;
let params: ShellCommandToolCallParams = parse_arguments_with_base_path(&arguments, &cwd)?;
let workdir = turn.resolve_path(params.workdir.clone());
maybe_emit_implicit_skill_invocation(
session.as_ref(),
@@ -466,15 +463,9 @@ impl ShellHandler {
}
// Intercept apply_patch if present.
let apply_patch_cwd =
AbsolutePathBuf::from_absolute_path(&exec_params.cwd).map_err(|err| {
FunctionCallError::RespondToModel(format!(
"apply_patch verification failed: failed to resolve cwd: {err}"
))
})?;
if let Some(output) = intercept_apply_patch(
&exec_params.command,
&apply_patch_cwd,
&exec_params.cwd,
fs.as_ref(),
exec_params.expiration.timeout_ms(),
session.clone(),
@@ -491,7 +482,7 @@ impl ShellHandler {
let source = ExecCommandSource::Agent;
let emitter = ToolEmitter::shell(
exec_params.command.clone(),
exec_params.cwd.clone(),
exec_params.cwd.to_path_buf(),
source,
freeform,
);
@@ -30,7 +30,6 @@ use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::TerminalInteractionEvent;
use codex_shell_command::is_safe_command::is_known_safe_command;
use codex_tools::UnifiedExecShellMode;
use codex_utils_absolute_path::AbsolutePathBuf;
use serde::Deserialize;
use std::path::PathBuf;
use std::sync::Arc;
@@ -189,9 +188,8 @@ impl ToolHandler for UnifiedExecHandler {
let response = match tool_name.as_str() {
"exec_command" => {
let cwd = resolve_workdir_base_path(&arguments, context.turn.cwd.as_path())?;
let args: ExecCommandArgs =
parse_arguments_with_base_path(&arguments, cwd.as_path())?;
let cwd = resolve_workdir_base_path(&arguments, &context.turn.cwd)?;
let args: ExecCommandArgs = parse_arguments_with_base_path(&arguments, &cwd)?;
let workdir = context.turn.resolve_path(args.workdir.clone());
maybe_emit_implicit_skill_invocation(
session.as_ref(),
@@ -282,18 +280,9 @@ impl ToolHandler for UnifiedExecHandler {
}
};
let apply_patch_cwd = match AbsolutePathBuf::from_absolute_path(&cwd) {
Ok(cwd) => cwd,
Err(err) => {
manager.release_process_id(process_id).await;
return Err(FunctionCallError::RespondToModel(format!(
"apply_patch verification failed: failed to resolve cwd: {err}"
)));
}
};
if let Some(output) = intercept_apply_patch(
&command,
&apply_patch_cwd,
&cwd,
fs.as_ref(),
Some(yield_time_ms),
context.session.clone(),
@@ -7,6 +7,7 @@ use codex_protocol::models::PermissionProfile;
use codex_tools::UnifiedExecShellMode;
use codex_tools::ZshForkConfig;
use codex_utils_absolute_path::AbsolutePathBuf;
use core_test_support::PathExt;
use pretty_assertions::assert_eq;
use std::fs;
use std::sync::Arc;
@@ -181,15 +182,15 @@ fn exec_command_args_resolve_relative_additional_permissions_against_workdir() -
}
}"#;
let base_path = resolve_workdir_base_path(json, cwd.path())?;
let args: ExecCommandArgs = parse_arguments_with_base_path(json, base_path.as_path())?;
let base_path = resolve_workdir_base_path(json, &cwd.path().abs())?;
let args: ExecCommandArgs = parse_arguments_with_base_path(json, &base_path)?;
assert_eq!(
args.additional_permissions,
Some(PermissionProfile {
file_system: Some(FileSystemPermissions {
read: None,
write: Some(vec![AbsolutePathBuf::try_from(expected_write)?]),
write: Some(vec![expected_write.abs()]),
}),
..Default::default()
})
@@ -4,7 +4,6 @@ use codex_protocol::models::FunctionCallOutputPayload;
use codex_protocol::models::ImageDetail;
use codex_protocol::models::ResponseInputItem;
use codex_protocol::openai_models::InputModality;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_image::PromptImageMode;
use codex_utils_image::load_for_prompt_bytes;
use serde::Deserialize;
@@ -87,10 +86,7 @@ impl ToolHandler for ViewImageHandler {
}
};
let abs_path =
AbsolutePathBuf::try_from(turn.resolve_path(Some(args.path))).map_err(|error| {
FunctionCallError::RespondToModel(format!("unable to resolve image path: {error}"))
})?;
let abs_path = turn.resolve_path(Some(args.path));
let Some(environment) = turn.environment.as_ref() else {
return Err(FunctionCallError::RespondToModel(
"view_image is unavailable in this session".to_string(),
+2 -2
View File
@@ -1050,7 +1050,7 @@ impl JsReplManager {
"--experimental-vm-modules".to_string(),
kernel_path.to_string_lossy().to_string(),
],
cwd: turn.cwd.to_path_buf(),
cwd: turn.cwd.clone(),
env,
additional_permissions: None,
};
@@ -1068,7 +1068,7 @@ impl JsReplManager {
enforce_managed_network: has_managed_network_requirements,
network: None,
sandbox_policy_cwd: &turn.cwd,
codex_linux_sandbox_exe: turn.codex_linux_sandbox_exe.as_ref(),
codex_linux_sandbox_exe: turn.codex_linux_sandbox_exe.as_deref(),
use_legacy_landlock: turn.features.use_legacy_landlock(),
windows_sandbox_level: turn.windows_sandbox_level,
windows_sandbox_private_desktop: turn
@@ -104,7 +104,7 @@ impl ApplyPatchRuntime {
CODEX_CORE_APPLY_PATCH_ARG1.to_string(),
req.action.patch.clone(),
],
cwd: req.action.cwd.to_path_buf(),
cwd: req.action.cwd.clone(),
// Run apply_patch with a minimal environment for determinism and to avoid leaks.
env: HashMap::new(),
additional_permissions: req.additional_permissions.clone(),
+3 -2
View File
@@ -10,6 +10,7 @@ use crate::shell::Shell;
use crate::tools::sandboxing::ToolError;
use codex_protocol::models::PermissionProfile;
use codex_sandboxing::SandboxCommand;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::collections::HashMap;
use std::path::Path;
@@ -21,7 +22,7 @@ pub(crate) mod unified_exec;
/// Validates that at least a program is present.
pub(crate) fn build_sandbox_command(
command: &[String],
cwd: &Path,
cwd: &AbsolutePathBuf,
env: &HashMap<String, String>,
additional_permissions: Option<PermissionProfile>,
) -> Result<SandboxCommand, ToolError> {
@@ -31,7 +32,7 @@ pub(crate) fn build_sandbox_command(
Ok(SandboxCommand {
program: program.clone().into(),
args: args.to_vec(),
cwd: cwd.to_path_buf(),
cwd: cwd.clone(),
env: env.clone(),
additional_permissions,
})
+4 -4
View File
@@ -38,14 +38,14 @@ use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::ReviewDecision;
use codex_sandboxing::SandboxablePreference;
use codex_shell_command::powershell::prefix_powershell_script_with_utf8;
use codex_utils_absolute_path::AbsolutePathBuf;
use futures::future::BoxFuture;
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Clone, Debug)]
pub struct ShellRequest {
pub command: Vec<String>,
pub cwd: PathBuf,
pub cwd: AbsolutePathBuf,
pub timeout_ms: Option<u64>,
pub env: HashMap<String, String>,
pub explicit_env_overrides: HashMap<String, String>,
@@ -93,7 +93,7 @@ pub struct ShellRuntime {
#[derive(serde::Serialize, Clone, Debug, Eq, PartialEq, Hash)]
pub(crate) struct ApprovalKey {
command: Vec<String>,
cwd: PathBuf,
cwd: AbsolutePathBuf,
sandbox_permissions: SandboxPermissions,
additional_permissions: Option<PermissionProfile>,
}
@@ -146,7 +146,7 @@ impl Approvable<ShellRequest> for ShellRuntime {
) -> BoxFuture<'a, ReviewDecision> {
let keys = self.approval_keys(req);
let command = req.command.clone();
let cwd = req.cwd.clone();
let cwd = req.cwd.to_path_buf();
let retry_reason = ctx.retry_reason.clone();
let reason = retry_reason.clone().or_else(|| req.justification.clone());
let session = ctx.session;
@@ -658,7 +658,7 @@ fn commands_for_intercepted_exec_policy(
struct CoreShellCommandExecutor {
command: Vec<String>,
cwd: PathBuf,
cwd: AbsolutePathBuf,
sandbox_policy: SandboxPolicy,
file_system_sandbox_policy: FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
@@ -827,7 +827,7 @@ impl CoreShellCommandExecutor {
let command = SandboxCommand {
program: program.clone().into(),
args: args.to_vec(),
cwd: workdir.to_path_buf(),
cwd: workdir.clone(),
env,
additional_permissions,
};
@@ -844,7 +844,7 @@ impl CoreShellCommandExecutor {
enforce_managed_network: self.network.is_some(),
network: self.network.as_ref(),
sandbox_policy_cwd: &self.sandbox_policy_cwd,
codex_linux_sandbox_exe: self.codex_linux_sandbox_exe.as_ref(),
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,
windows_sandbox_private_desktop: false,
@@ -857,7 +857,7 @@ impl CoreShellCommandExecutor {
Ok(PreparedExec {
command: exec_request.command,
cwd: exec_request.cwd,
cwd: exec_request.cwd.to_path_buf(),
env: exec_request.env,
arg0: exec_request.arg0,
})
@@ -41,9 +41,9 @@ use codex_protocol::protocol::ReviewDecision;
use codex_sandboxing::SandboxablePreference;
use codex_shell_command::powershell::prefix_powershell_script_with_utf8;
use codex_tools::UnifiedExecShellMode;
use codex_utils_absolute_path::AbsolutePathBuf;
use futures::future::BoxFuture;
use std::collections::HashMap;
use std::path::PathBuf;
/// Request payload used by the unified-exec runtime after approvals and
/// sandbox preferences have been resolved for the current turn.
@@ -51,7 +51,7 @@ use std::path::PathBuf;
pub struct UnifiedExecRequest {
pub command: Vec<String>,
pub process_id: i32,
pub cwd: PathBuf,
pub cwd: AbsolutePathBuf,
pub env: HashMap<String, String>,
pub explicit_env_overrides: HashMap<String, String>,
pub network: Option<NetworkProxy>,
@@ -69,7 +69,7 @@ pub struct UnifiedExecRequest {
#[derive(serde::Serialize, Clone, Debug, Eq, PartialEq, Hash)]
pub struct UnifiedExecApprovalKey {
pub command: Vec<String>,
pub cwd: PathBuf,
pub cwd: AbsolutePathBuf,
pub tty: bool,
pub sandbox_permissions: SandboxPermissions,
pub additional_permissions: Option<PermissionProfile>,
@@ -125,7 +125,7 @@ impl Approvable<UnifiedExecRequest> for UnifiedExecRuntime<'_> {
let turn = ctx.turn;
let call_id = ctx.call_id.to_string();
let command = req.command.clone();
let cwd = req.cwd.clone();
let cwd = req.cwd.to_path_buf();
let retry_reason = ctx.retry_reason.clone();
let reason = retry_reason.clone().or_else(|| req.justification.clone());
Box::pin(async move {
+3 -1
View File
@@ -348,7 +348,9 @@ impl<'a> SandboxAttempt<'a> {
enforce_managed_network: self.enforce_managed_network,
network,
sandbox_policy_cwd: self.sandbox_cwd,
codex_linux_sandbox_exe: self.codex_linux_sandbox_exe,
codex_linux_sandbox_exe: self
.codex_linux_sandbox_exe
.map(std::path::PathBuf::as_path),
use_legacy_landlock: self.use_legacy_landlock,
windows_sandbox_level: self.windows_sandbox_level,
windows_sandbox_private_desktop: self.windows_sandbox_private_desktop,
+2 -2
View File
@@ -24,12 +24,12 @@
use std::collections::HashMap;
use std::collections::HashSet;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::Weak;
use codex_network_proxy::NetworkProxy;
use codex_protocol::models::PermissionProfile;
use codex_utils_absolute_path::AbsolutePathBuf;
use rand::Rng;
use rand::rng;
use tokio::sync::Mutex;
@@ -91,7 +91,7 @@ pub(crate) struct ExecCommandRequest {
pub process_id: i32,
pub yield_time_ms: u64,
pub max_output_tokens: Option<usize>,
pub workdir: Option<PathBuf>,
pub workdir: Option<AbsolutePathBuf>,
pub network: Option<NetworkProxy>,
pub tty: bool,
pub sandbox_permissions: SandboxPermissions,
+7 -5
View File
@@ -51,7 +51,7 @@ fn shell_env() -> HashMap<String, String> {
fn test_exec_request(
turn: &TurnContext,
command: Vec<String>,
cwd: PathBuf,
cwd: AbsolutePathBuf,
env: HashMap<String, String>,
) -> ExecRequest {
let windows_sandbox_private_desktop = false;
@@ -87,7 +87,9 @@ async fn exec_command_with_tty(
) -> Result<ExecCommandToolOutput, UnifiedExecError> {
let manager = &session.services.unified_exec_manager;
let process_id = manager.allocate_process_id().await;
let cwd = workdir.unwrap_or_else(|| turn.cwd.clone().to_path_buf());
let cwd = workdir
.as_ref()
.map_or_else(|| turn.cwd.clone(), |workdir| turn.cwd.join(workdir));
let command = vec!["bash".to_string(), "-lc".to_string(), cmd.to_string()];
let request = test_exec_request(turn, command.clone(), cwd.clone(), shell_env());
@@ -502,7 +504,7 @@ async fn completed_pipe_commands_preserve_exit_code() -> anyhow::Result<()> {
let request = test_exec_request(
&turn,
vec!["bash".to_string(), "-lc".to_string(), "exit 17".to_string()],
PathBuf::from("/tmp"),
turn.cwd.clone(),
shell_env(),
);
@@ -544,7 +546,7 @@ async fn unified_exec_uses_remote_exec_server_when_configured() -> anyhow::Resul
let request = test_exec_request(
&turn,
vec!["bash".to_string(), "-i".to_string()],
PathBuf::from("/tmp"),
remote_test_env.cwd().clone(),
shell_env(),
);
@@ -598,7 +600,7 @@ async fn remote_exec_server_rejects_inherited_fd_launches() -> anyhow::Result<()
let request = test_exec_request(
&turn,
vec!["bash".to_string(), "-lc".to_string(), "echo ok".to_string()],
PathBuf::from("/tmp"),
turn.cwd.clone(),
shell_env(),
);
@@ -2,7 +2,6 @@ use rand::Rng;
use std::cmp::Reverse;
use std::collections::HashMap;
use std::collections::HashSet;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
@@ -49,6 +48,7 @@ use crate::unified_exec::process::OutputHandles;
use crate::unified_exec::process::SpawnLifecycleHandle;
use crate::unified_exec::process::UnifiedExecProcess;
use codex_protocol::protocol::ExecCommandSource;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_output_truncation::approx_token_count;
const UNIFIED_EXEC_ENV: [(&str, &str); 10] = [
@@ -165,7 +165,7 @@ impl UnifiedExecProcessManager {
let cwd = request
.workdir
.clone()
.unwrap_or_else(|| context.turn.cwd.to_path_buf());
.unwrap_or_else(|| context.turn.cwd.clone());
let process = self
.open_session_with_sandbox(&request, cwd.clone(), context)
.await;
@@ -189,7 +189,7 @@ impl UnifiedExecProcessManager {
);
let emitter = ToolEmitter::unified_exec(
&request.command,
cwd.clone(),
cwd.to_path_buf(),
ExecCommandSource::UnifiedExecStartup,
Some(request.process_id.to_string()),
);
@@ -255,7 +255,7 @@ impl UnifiedExecProcessManager {
Arc::clone(&context.turn),
context.call_id.clone(),
request.command.clone(),
cwd.clone(),
cwd.to_path_buf(),
Some(request.process_id.to_string()),
Arc::clone(&transcript),
message.clone(),
@@ -298,7 +298,7 @@ impl UnifiedExecProcessManager {
Arc::clone(&context.turn),
context.call_id.clone(),
request.command.clone(),
cwd.clone(),
cwd.to_path_buf(),
Some(process_id.to_string()),
Arc::clone(&transcript),
text.clone(),
@@ -526,7 +526,7 @@ impl UnifiedExecProcessManager {
process: Arc<UnifiedExecProcess>,
context: &UnifiedExecContext,
command: &[String],
cwd: PathBuf,
cwd: AbsolutePathBuf,
started_at: Instant,
process_id: i32,
tty: bool,
@@ -572,7 +572,7 @@ impl UnifiedExecProcessManager {
Arc::clone(&context.turn),
context.call_id.clone(),
command.to_vec(),
cwd,
cwd.to_path_buf(),
process_id,
transcript,
started_at,
@@ -605,7 +605,7 @@ impl UnifiedExecProcessManager {
.start(codex_exec_server::ExecParams {
process_id: exec_server_process_id(process_id).into(),
argv: request.command.clone(),
cwd: request.cwd.clone(),
cwd: request.cwd.to_path_buf(),
env: request.env.clone(),
tty,
arg0: request.arg0.clone(),
@@ -646,7 +646,7 @@ impl UnifiedExecProcessManager {
pub(super) async fn open_session_with_sandbox(
&self,
request: &ExecCommandRequest,
cwd: PathBuf,
cwd: AbsolutePathBuf,
context: &UnifiedExecContext,
) -> Result<(UnifiedExecProcess, Option<DeferredNetworkApproval>), UnifiedExecError> {
let env = apply_unified_exec_env(create_env(
+2 -1
View File
@@ -16,6 +16,7 @@ use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::SandboxPolicy;
use codex_sandboxing::SandboxType;
use codex_sandboxing::get_platform_sandbox;
use core_test_support::PathExt;
use tempfile::TempDir;
fn skip_test() -> bool {
@@ -35,7 +36,7 @@ async fn run_test_cmd(tmp: TempDir, cmd: Vec<&str>) -> Result<ExecToolCallOutput
let params = ExecParams {
command: cmd.iter().map(ToString::to_string).collect(),
cwd: tmp.path().to_path_buf(),
cwd: tmp.path().abs(),
expiration: 1000.into(),
capture_policy: ExecCapturePolicy::ShellTool,
env: HashMap::new(),
@@ -117,7 +117,7 @@ async fn run_cmd_result_with_policies(
timeout_ms: u64,
use_legacy_landlock: bool,
) -> Result<codex_protocol::exec_output::ExecToolCallOutput> {
let cwd = std::env::current_dir().expect("cwd should exist");
let cwd = AbsolutePathBuf::current_dir().expect("cwd should exist");
let sandbox_cwd = cwd.clone();
let params = ExecParams {
command: cmd.iter().copied().map(str::to_owned).collect(),
@@ -375,7 +375,7 @@ async fn test_timeout() {
/// suite remains green on leaner CI images.
#[expect(clippy::expect_used)]
async fn assert_network_blocked(cmd: &[&str]) {
let cwd = std::env::current_dir().expect("cwd should exist");
let cwd = AbsolutePathBuf::current_dir().expect("cwd should exist");
let sandbox_cwd = cwd.clone();
let params = ExecParams {
command: cmd.iter().copied().map(str::to_owned).collect(),
+5 -8
View File
@@ -15,10 +15,10 @@ use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::SandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::collections::HashMap;
use std::ffi::OsString;
use std::path::Path;
use std::path::PathBuf;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SandboxType {
@@ -66,7 +66,7 @@ pub fn get_platform_sandbox(windows_sandbox_enabled: bool) -> Option<SandboxType
pub struct SandboxCommand {
pub program: OsString,
pub args: Vec<String>,
pub cwd: PathBuf,
pub cwd: AbsolutePathBuf,
pub env: HashMap<String, String>,
pub additional_permissions: Option<PermissionProfile>,
}
@@ -74,7 +74,7 @@ pub struct SandboxCommand {
#[derive(Debug)]
pub struct SandboxExecRequest {
pub command: Vec<String>,
pub cwd: PathBuf,
pub cwd: AbsolutePathBuf,
pub env: HashMap<String, String>,
pub network: Option<NetworkProxy>,
pub sandbox: SandboxType,
@@ -100,7 +100,7 @@ pub struct SandboxTransformRequest<'a> {
// to make shared ownership explicit across runtime/sandbox plumbing.
pub network: Option<&'a NetworkProxy>,
pub sandbox_policy_cwd: &'a Path,
pub codex_linux_sandbox_exe: Option<&'a PathBuf>,
pub codex_linux_sandbox_exe: Option<&'a Path>,
pub use_legacy_landlock: bool,
pub windows_sandbox_level: WindowsSandboxLevel,
pub windows_sandbox_private_desktop: bool,
@@ -232,10 +232,7 @@ impl SandboxManager {
let mut full_command = Vec::with_capacity(1 + args.len());
full_command.push(os_string_to_command_component(exe.as_os_str().to_owned()));
full_command.append(&mut args);
(
full_command,
Some(linux_sandbox_arg0_override(exe.as_path())),
)
(full_command, Some(linux_sandbox_arg0_override(exe)))
}
#[cfg(target_os = "windows")]
SandboxType::WindowsRestrictedToken => (os_argv_to_strings(argv), None),
+5 -5
View File
@@ -74,7 +74,7 @@ fn restricted_file_system_uses_platform_sandbox_without_managed_network() {
#[test]
fn transform_preserves_unrestricted_file_system_policy_for_restricted_network() {
let manager = SandboxManager::new();
let cwd = std::env::current_dir().expect("current dir");
let cwd = AbsolutePathBuf::current_dir().expect("current dir");
let exec_request = manager
.transform(SandboxTransformRequest {
command: SandboxCommand {
@@ -113,7 +113,7 @@ fn transform_preserves_unrestricted_file_system_policy_for_restricted_network()
#[test]
fn transform_additional_permissions_enable_network_for_external_sandbox() {
let manager = SandboxManager::new();
let cwd = std::env::current_dir().expect("current dir");
let cwd = AbsolutePathBuf::current_dir().expect("current dir");
let temp_dir = TempDir::new().expect("create temp dir");
let path = AbsolutePathBuf::from_absolute_path(
canonicalize(temp_dir.path()).expect("canonicalize temp dir"),
@@ -167,7 +167,7 @@ fn transform_additional_permissions_enable_network_for_external_sandbox() {
#[test]
fn transform_additional_permissions_preserves_denied_entries() {
let manager = SandboxManager::new();
let cwd = std::env::current_dir().expect("current dir");
let cwd = AbsolutePathBuf::current_dir().expect("current dir");
let temp_dir = TempDir::new().expect("create temp dir");
let workspace_root = AbsolutePathBuf::from_absolute_path(
canonicalize(temp_dir.path()).expect("canonicalize temp dir"),
@@ -247,10 +247,10 @@ fn transform_additional_permissions_preserves_denied_entries() {
#[cfg(target_os = "linux")]
fn transform_linux_seccomp_request(
codex_linux_sandbox_exe: &std::path::PathBuf,
codex_linux_sandbox_exe: &std::path::Path,
) -> super::SandboxExecRequest {
let manager = SandboxManager::new();
let cwd = std::env::current_dir().expect("current dir");
let cwd = AbsolutePathBuf::current_dir().expect("current dir");
manager
.transform(SandboxTransformRequest {
command: SandboxCommand {