`apply_patch` can delete without visible Guardian review and report Delete+Add as Add-only
What version of Codex CLI is running?
codex-cli 0.144.23
What subscription do you have?
Not relevant.
Which model were you using?
gpt-5.6-sol
What platform is your computer?
Linux x86_64.
What terminal emulator and version are you using (if applicable)?
Codex CLI TUI under tmux.
Codex doctor report
Not available because feedback is disabled.
What issue are you seeing?
I use Codex daily and first observed this behavior within the last two weeks. During large rewrites, the model has started issuing one built-in apply_patch call that deletes and then adds the same existing path.
The tool executes both operations, but the TUI reports only an added file with additions and zero deletions. In one observed incident, a 19,657-byte file was replaced by 6,799 bytes, while patch_apply_end reported:
{
"change_type": "add",
"content_bytes": 6799,
"old_content_bytes": 0,
"unified_diff_bytes": 0
}
The deleted content is absent from the structured record. Without Git history or a backup, it cannot be recovered or audited. I have observed this pattern affect multiple files.
apply_patch is not merely a diff renderer: its patch language accepts *** Delete File, and its runtime handles Hunk::DeleteFile by calling the filesystem remove operation directly. assess_patch_safety treats Add and Delete alike when the path is writable and can auto-approve the patch, skipping an approval prompt.
No rm or unlink command is emitted. Guardian review that detects destructive shell commands therefore receives no shell deletion to review. In this same-path case, the path-keyed change map also retains only the final Add, so the patch approval and event surfaces do not expose the deletion either. The result is a built-in file-deletion capability that, in practice, bypasses the visible destructive review while presenting the operation as Add-only.
What steps can reproduce the bug?
The actual observed tool call contained:
*** Begin Patch
*** Delete File: <existing-file>
*** Add File: <existing-file>
+<replacement content>
*** End Patch
After it succeeds:
- The existing file has been replaced.
- The TUI shows
Added (+N -0). patch_apply_endcontains onlyFileChange::Add.- No Guardian destructive-file review is shown.
- No before-content or deletion is present in the audit record.
What is the expected behavior?
apply_patch should reject multiple file-level operations for the same resolved path before mutation.
If Delete File remains available, it must be treated as a destructive operation and require an explicit Guardian-visible review even inside writable roots. If whole-file replacement is supported, it must be represented explicitly. Approval UI, hooks, TUI output, and audit events must show the deletion and preserve the before-state.
Additional information
The current source uses a path-keyed HashMap<PathUri, ApplyPatchFileChange> for verification and presentation in codex-rs/apply-patch/src/invocation.rs. A later Add for the same path overwrites the earlier Delete in that map. The runtime separately executes the raw patch hunks in order; codex-rs/apply-patch/src/lib.rs implements Hunk::DeleteFile with fs.remove. This explains why the filesystem receives Delete+Add while safety, approval, and event representations can expose only Add.
Codex CLI assisted with organizing this report and the public source analysis.