diff --git a/codex-rs/apply-patch/src/lib.rs b/codex-rs/apply-patch/src/lib.rs index de22173c..5bda31c4 100644 --- a/codex-rs/apply-patch/src/lib.rs +++ b/codex-rs/apply-patch/src/lib.rs @@ -40,6 +40,11 @@ pub enum ApplyPatchError { /// Error that occurs while computing replacements when applying patch chunks #[error("{0}")] ComputeReplacements(String), + /// A raw patch body was provided without an explicit `apply_patch` invocation. + #[error( + "patch detected without explicit call to apply_patch. Rerun as [\"apply_patch\", \"\"]" + )] + ImplicitInvocation, } impl From for ApplyPatchError { @@ -93,10 +98,12 @@ pub struct ApplyPatchArgs { pub fn maybe_parse_apply_patch(argv: &[String]) -> MaybeApplyPatch { match argv { + // Direct invocation: apply_patch [cmd, body] if APPLY_PATCH_COMMANDS.contains(&cmd.as_str()) => match parse_patch(body) { Ok(source) => MaybeApplyPatch::Body(source), Err(e) => MaybeApplyPatch::PatchParseError(e), }, + // Bash heredoc form: (optional `cd &&`) apply_patch <<'EOF' ... [bash, flag, script] if bash == "bash" && flag == "-lc" => { match extract_apply_patch_from_bash(script) { Ok((body, workdir)) => match parse_patch(&body) { @@ -207,6 +214,26 @@ impl ApplyPatchAction { /// cwd must be an absolute path so that we can resolve relative paths in the /// patch. pub fn maybe_parse_apply_patch_verified(argv: &[String], cwd: &Path) -> MaybeApplyPatchVerified { + // Detect a raw patch body passed directly as the command or as the body of a bash -lc + // script. In these cases, report an explicit error rather than applying the patch. + match argv { + [body] => { + if parse_patch(body).is_ok() { + return MaybeApplyPatchVerified::CorrectnessError( + ApplyPatchError::ImplicitInvocation, + ); + } + } + [bash, flag, script] if bash == "bash" && flag == "-lc" => { + if parse_patch(script).is_ok() { + return MaybeApplyPatchVerified::CorrectnessError( + ApplyPatchError::ImplicitInvocation, + ); + } + } + _ => {} + } + match maybe_parse_apply_patch(argv) { MaybeApplyPatch::Body(ApplyPatchArgs { patch, @@ -875,6 +902,28 @@ mod tests { )); } + #[test] + fn test_implicit_patch_single_arg_is_error() { + let patch = "*** Begin Patch\n*** Add File: foo\n+hi\n*** End Patch".to_string(); + let args = vec![patch]; + let dir = tempdir().unwrap(); + assert!(matches!( + maybe_parse_apply_patch_verified(&args, dir.path()), + MaybeApplyPatchVerified::CorrectnessError(ApplyPatchError::ImplicitInvocation) + )); + } + + #[test] + fn test_implicit_patch_bash_script_is_error() { + let script = "*** Begin Patch\n*** Add File: foo\n+hi\n*** End Patch"; + let args = args_bash(script); + let dir = tempdir().unwrap(); + assert!(matches!( + maybe_parse_apply_patch_verified(&args, dir.path()), + MaybeApplyPatchVerified::CorrectnessError(ApplyPatchError::ImplicitInvocation) + )); + } + #[test] fn test_literal() { let args = strs_to_strings(&[