Compare commits

...

1 Commits

Author SHA1 Message Date
David Wiesen
56a7f3c5b7 fix: avoid large apply_patch argv on Windows 2026-04-08 10:16:17 -07:00
4 changed files with 117 additions and 8 deletions

View File

@@ -38,6 +38,7 @@ pub const APPLY_PATCH_TOOL_INSTRUCTIONS: &str = include_str!("../apply_patch_too
/// process-invocation contract between the apply-patch runtime and the arg0
/// dispatcher.
pub const CODEX_CORE_APPLY_PATCH_ARG1: &str = "--codex-run-as-apply-patch";
pub const CODEX_CORE_APPLY_PATCH_FILE_ARG1: &str = "--codex-run-as-apply-patch-file";
#[derive(Debug, Error, PartialEq)]
pub enum ApplyPatchError {

View File

@@ -4,6 +4,7 @@ use std::path::Path;
use std::path::PathBuf;
use codex_apply_patch::CODEX_CORE_APPLY_PATCH_ARG1;
use codex_apply_patch::CODEX_CORE_APPLY_PATCH_FILE_ARG1;
use codex_sandboxing::landlock::CODEX_LINUX_SANDBOX_ARG0;
use codex_utils_home_dir::find_codex_home;
#[cfg(unix)]
@@ -93,10 +94,23 @@ pub fn arg0_dispatch() -> Option<Arg0PathEntryGuard> {
}
let argv1 = args.next().unwrap_or_default();
if argv1 == CODEX_CORE_APPLY_PATCH_ARG1 {
if argv1 == CODEX_CORE_APPLY_PATCH_ARG1 || argv1 == CODEX_CORE_APPLY_PATCH_FILE_ARG1 {
let patch_arg = args.next().and_then(|s| s.to_str().map(str::to_owned));
let exit_code = match patch_arg {
Some(patch_arg) => {
let patch = if argv1 == CODEX_CORE_APPLY_PATCH_FILE_ARG1 {
match std::fs::read_to_string(&patch_arg) {
Ok(patch) => patch,
Err(err) => {
eprintln!(
"Error: failed to read apply_patch file {patch_arg:?}: {err}"
);
std::process::exit(1);
}
}
} else {
patch_arg
};
let mut stdout = std::io::stdout();
let mut stderr = std::io::stderr();
let cwd = match codex_utils_absolute_path::AbsolutePathBuf::current_dir() {
@@ -111,7 +125,7 @@ pub fn arg0_dispatch() -> Option<Arg0PathEntryGuard> {
Err(_) => std::process::exit(1),
};
match runtime.block_on(codex_apply_patch::apply_patch(
&patch_arg,
&patch,
&cwd,
&mut stdout,
&mut stderr,
@@ -122,7 +136,7 @@ pub fn arg0_dispatch() -> Option<Arg0PathEntryGuard> {
}
}
None => {
eprintln!("Error: {CODEX_CORE_APPLY_PATCH_ARG1} requires a UTF-8 PATCH argument.");
eprintln!("Error: {argv1:?} requires a UTF-8 PATCH argument.");
1
}
};

View File

@@ -22,6 +22,8 @@ use crate::tools::sandboxing::ToolRuntime;
use crate::tools::sandboxing::with_cached_approval;
use codex_apply_patch::ApplyPatchAction;
use codex_apply_patch::CODEX_CORE_APPLY_PATCH_ARG1;
#[cfg(target_os = "windows")]
use codex_apply_patch::CODEX_CORE_APPLY_PATCH_FILE_ARG1;
use codex_protocol::exec_output::ExecToolCallOutput;
use codex_protocol::exec_output::StreamOutput;
use codex_protocol::models::PermissionProfile;
@@ -35,6 +37,8 @@ use futures::future::BoxFuture;
use std::collections::HashMap;
use std::path::PathBuf;
use std::time::Instant;
#[cfg(target_os = "windows")]
use tempfile::NamedTempFile;
#[derive(Debug)]
pub struct ApplyPatchRequest {
@@ -98,12 +102,24 @@ impl ApplyPatchRuntime {
}
fn build_sandbox_command_with_program(req: &ApplyPatchRequest, exe: PathBuf) -> SandboxCommand {
SandboxCommand {
program: exe.into_os_string(),
args: vec![
Self::build_sandbox_command_with_args(
req,
exe,
vec![
CODEX_CORE_APPLY_PATCH_ARG1.to_string(),
req.action.patch.clone(),
],
)
}
fn build_sandbox_command_with_args(
req: &ApplyPatchRequest,
exe: PathBuf,
args: Vec<String>,
) -> SandboxCommand {
SandboxCommand {
program: exe.into_os_string(),
args,
cwd: req.action.cwd.to_path_buf(),
// Run apply_patch with a minimal environment for determinism and to avoid leaks.
env: HashMap::new(),
@@ -111,6 +127,34 @@ impl ApplyPatchRuntime {
}
}
#[cfg(target_os = "windows")]
fn build_sandbox_command_with_patch_file(
req: &ApplyPatchRequest,
codex_home: &std::path::Path,
patch_file: &std::path::Path,
) -> SandboxCommand {
Self::build_sandbox_command_with_args(
req,
codex_windows_sandbox::resolve_current_exe_for_launch(codex_home, "codex.exe"),
vec![
CODEX_CORE_APPLY_PATCH_FILE_ARG1.to_string(),
patch_file.to_string_lossy().into_owned(),
],
)
}
#[cfg(target_os = "windows")]
fn write_patch_file(req: &ApplyPatchRequest) -> Result<NamedTempFile, ToolError> {
let mut file = tempfile::Builder::new()
.prefix(".codex-apply-patch-")
.suffix(".patch")
.tempfile_in(req.action.cwd.as_path())
.map_err(|err| ToolError::Codex(anyhow::Error::from(err)))?;
std::io::Write::write_all(&mut file, req.action.patch.as_bytes())
.map_err(|err| ToolError::Codex(anyhow::Error::from(err)))?;
Ok(file)
}
fn stdout_stream(ctx: &ToolCtx) -> Option<crate::exec::StdoutStream> {
Some(crate::exec::StdoutStream {
sub_id: ctx.turn.sub_id.clone(),
@@ -241,7 +285,13 @@ impl ToolRuntime<ApplyPatchRequest, ExecToolCallOutput> for ApplyPatchRuntime {
}
#[cfg(target_os = "windows")]
let command = Self::build_sandbox_command(req, &ctx.turn.config.codex_home)?;
let patch_file = Self::write_patch_file(req)?;
#[cfg(target_os = "windows")]
let command = Self::build_sandbox_command_with_patch_file(
req,
&ctx.turn.config.codex_home,
patch_file.path(),
);
#[cfg(not(target_os = "windows"))]
let command = Self::build_sandbox_command(req, ctx.turn.codex_self_exe.as_ref())?;
let options = ExecOptions {

View File

@@ -1,9 +1,9 @@
use super::*;
use codex_apply_patch::CODEX_CORE_APPLY_PATCH_FILE_ARG1;
use codex_protocol::protocol::GranularApprovalConfig;
use core_test_support::PathBufExt;
use pretty_assertions::assert_eq;
use std::collections::HashMap;
#[cfg(not(target_os = "windows"))]
use std::path::PathBuf;
#[test]
@@ -136,3 +136,47 @@ fn build_sandbox_command_falls_back_to_current_exe_for_apply_patch() {
.into_os_string()
);
}
#[test]
fn build_sandbox_command_can_use_file_backed_patch_payload() {
let path = std::env::temp_dir()
.join("apply-patch-file-backed-test.txt")
.abs();
let action = ApplyPatchAction::new_add_for_test(&path, "hello".to_string());
let request = ApplyPatchRequest {
action,
file_paths: vec![path.clone()],
changes: HashMap::from([(
path.to_path_buf(),
FileChange::Add {
content: "hello".to_string(),
},
)]),
exec_approval_requirement: ExecApprovalRequirement::NeedsApproval {
reason: None,
proposed_execpolicy_amendment: None,
},
additional_permissions: None,
permissions_preapproved: false,
timeout_ms: None,
};
let patch_file = PathBuf::from(r"C:\tmp\large.patch");
let command = ApplyPatchRuntime::build_sandbox_command_with_args(
&request,
PathBuf::from(r"C:\codex.exe"),
vec![
CODEX_CORE_APPLY_PATCH_FILE_ARG1.to_string(),
patch_file.to_string_lossy().into_owned(),
],
);
assert_eq!(
command.args,
vec![
CODEX_CORE_APPLY_PATCH_FILE_ARG1.to_string(),
patch_file.to_string_lossy().into_owned(),
]
);
assert!(!command.args.iter().any(|arg| arg == &request.action.patch));
}