apply_patch Add File silently overwrites existing files
What version of Codex CLI is running?
codex-cli 0.135.0
Which model were you using?
N/A. This reproduces through apply_patch directly and does not depend on model behaviour.
What platform is your computer?
Linux 7.0.10-2-cachyos x86_64
What terminal emulator and version are you using (if applicable)?
xterm-256color, shell /bin/fish
What issue are you seeing?
apply_patch treats *** Add File as an overwrite operation when the target path already exists.
This appears to contradict the Codex apply-patch tool contract. The tool instructions describe *** Add File: as creating a new file, but the current behaviour accepts an existing file path and replaces its contents without warning.
This matters because it appears to be the harness/tool-level cause behind #10037, where *** Add File was used on a .env path that already existed as a symlink. The tool followed the symlink and overwrote the target contents. That issue was discussed partly as model behaviour, but the dangerous part is reproducible without the model: Add File does not fail when the file already exists.
What steps can reproduce the bug?
Create a disposable file:
tmpdir="$(mktemp -d)"
printf 'original line 1\noriginal line 2\n' > "$tmpdir/existing.txt"
nl -ba "$tmpdir/existing.txt"
Apply an Add File patch to the same existing path:
*** Begin Patch
*** Add File: /tmp/.../existing.txt
+new content
*** End Patch
Observed result:
Success. Updated the following files:
A /tmp/.../existing.txt
The file is overwritten:
new content
There is no warning that the target already existed, and the summary reports it as added rather than overwritten.
What is the expected behavior?
*** Add File should fail if the target path already exists.
Suggested stricter semantics:
Add File/ create should fail when the path exists.Update Fileshould fail when the path does not exist.- Overwrite should require an explicit operation or explicit flag.
- If an overwrite is intentionally allowed, the success summary should say that an existing file was overwritten.
This is especially important for symlinked paths. A create operation should not silently follow a symlink and replace the linked target.
Additional information
Related issue: #10037
In that issue, a .env file in the workspace was a symlink to a file outside the workdir. Codex used *** Add File and the existing target contents were replaced. The reporter lost existing env vars and noted that this seemed like a harness issue rather than model training.
The current behaviour also makes audit trails misleading: A path suggests creation, while the actual operation can be destructive replacement.
5 Comments
Hi, I’d like to work on this. I’ll submit a PR shortly.
I implemented a fix on a fork branch, but GitHub is blocking PR creation for me with
nightcityblade does not have the correct permissions to execute CreatePullRequest. I do not want to leave this claimed, so I am unclaiming it. Branch for reference: https://github.com/nightcityblade/codex/tree/fix/issue-25399I added a small external guard for this exact failure mode in case it helps people validate generated patches while the Codex-side semantics are being fixed.
It fails before applying a patch when:
*** Add Filetargets a path that already exists, including symlinks*** Update Fileor*** Delete Filetargets a missing path*** Move towould overwrite an existing destinationTry it on a disposable patch file:
For this issue class, the expected result is a non-zero exit with JSON like:
Public fixture/demo for the report shape:
Release notes: https://github.com/grnbtqdbyx-create/trace-to-skill/releases/tag/v0.1.53
I reproduced this against current
mainwith the standalonecodex-apply-patchbinary.Repro shape:
Observed result: the command succeeds, reports the path as
A, and the previous file contents are replaced bynew content.I also found this behavior encoded in the existing fixture
codex-rs/apply-patch/tests/fixtures/scenarios/011_add_overwrites_existing_file: the input hasduplicate.txtwith old content, the patch uses*** Add File: duplicate.txt, and the expected output is the new content.Root cause in current code:
codex-rs/apply-patch/src/lib.rshandlesHunk::AddFileby callingread_optional_file_text_for_delta, thenwrite_file_with_missing_parent_retry, and records the change asAppliedPatchFileChange::Add { content, overwritten_content }. There is no existence guard before the write, and the summary path is still pushed intoadded, so the user-visible result saysAeven when an existing file was overwritten.Why this seems P0/high severity:
Add Fileis a tool contract that normally means fail-if-exists. Silent overwrite is data-loss risk, and the symlink case mentioned in #10037 makes it more dangerous because the affected target may not be the visible path the model/user expected to create.Minimal fix shape: make
Add Filefail when the destination already exists or is a symlink. If overwrite is intentionally supported, it should require an explicit operation/flag and report as an overwrite/modify, notA. Regression coverage should include at least an existing regular file and a symlink path.Per
docs/contributing.md, I am not opening an unsolicited PR; I can submit a focused patch if a maintainer says this direction is welcome.This is a dangerous semantic mismatch.
Add Fileshould be fail-if-exists; a destructive overwrite must require an explicit operation and be reported as a modification, never asA.The current behavior can silently destroy unrelated data through symlinks, as in #10037, especially when the model does not recognize that the apparent path is a symlink.