apply_patch Add File silently overwrites existing files

Open 💬 5 comments Opened May 31, 2026 by Isoceth

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 File should 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.

View original on GitHub ↗

5 Comments

nightcityblade · 2 months ago

Hi, I’d like to work on this. I’ll submit a PR shortly.

nightcityblade · 2 months ago

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-25399

grnbtqdbyx-create · 2 months ago

I 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 File targets a path that already exists, including symlinks
  • *** Update File or *** Delete File targets a missing path
  • *** Move to would overwrite an existing destination

Try it on a disposable patch file:

npx trace-to-skill@latest guard-patch ./change.patch --root . --format json

For this issue class, the expected result is a non-zero exit with JSON like:

{
  "status": "fail",
  "findings": [
    {
      "operation": "add",
      "target": "existing.txt",
      "message": "Add File target already exists; use Update File or remove the file first."
    }
  ]
}

Public fixture/demo for the report shape:

npx trace-to-skill@latest demo patch-overwrite --format json

Release notes: https://github.com/grnbtqdbyx-create/trace-to-skill/releases/tag/v0.1.53

wgu9 · 2 months ago

I reproduced this against current main with the standalone codex-apply-patch binary.

Repro shape:

printf 'original line 1\noriginal line 2\n' > existing.txt
cargo run -q -p codex-apply-patch --bin apply_patch -- '*** Begin Patch
*** Add File: existing.txt
+new content
*** End Patch'

Observed result: the command succeeds, reports the path as A, and the previous file contents are replaced by new 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 has duplicate.txt with 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.rs handles Hunk::AddFile by calling read_optional_file_text_for_delta, then write_file_with_missing_parent_retry, and records the change as AppliedPatchFileChange::Add { content, overwritten_content }. There is no existence guard before the write, and the summary path is still pushed into added, so the user-visible result says A even when an existing file was overwritten.

Why this seems P0/high severity: Add File is 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 File fail 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, not A. 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.

zombie12138 · 1 month ago

This is a dangerous semantic mismatch. Add File should be fail-if-exists; a destructive overwrite must require an explicit operation and be reported as a modification, never as A.

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.