Local file edits can accidentally rewrite whole files and overwrite existing changes

Open 💬 6 comments Opened Jul 6, 2026 by bryanxtong

What version of Codex CLI is running?

codex-cli 0.142.5

What subscription do you have?

gpt-5.4-mini

Which model were you using?

gpt-5.4-mini

What platform is your computer?

_No response_

What terminal emulator and version are you using (if applicable)?

_No response_

Codex doctor report

What issue are you seeing?

Description:
When making a small, targeted change to an existing source file, the editor sometimes rewrites much more than intended instead of applying a minimal patch. In practice, this can replace or corrupt unrelated
content in the same file and overwrite edits that were already present.

What happens:

  • A supposed one-line or small block change turns into a large file diff.
  • Existing modifications in the same file can be lost.
  • Unrelated content such as comments, spacing, or line endings may be changed.
  • The resulting diff becomes hard to review and may introduce accidental regressions.

Expected behavior:

  • Only the intended lines or code block should be modified.
  • Existing unrelated changes in the file must be preserved.
  • If the target context is ambiguous, the editor should stop and ask for confirmation instead of rewriting the file.

Impact:

  • High risk of overwriting user work in progress.
  • Makes code review unreliable.
  • Increases the chance of accidental bugs and unnecessary rollback.

Suggested fix:

  • Use true minimal patching for file edits.
  • Validate that the target context is unique before applying a change.
  • Avoid whole-file rewrite behavior for local edits.
  • Verify the resulting diff scope after editing and abort if it is larger than expected.

What steps can reproduce the bug?

• 1. Open an existing source file that already has uncommitted local changes.

  1. Make a small targeted edit request, such as changing one line inside a method.
  2. Apply the edit using the current file-edit workflow.
  3. Inspect git diff for that file.

Observed result

  • The diff is much larger than the intended change.
  • Unrelated lines, comments, or formatting in the same file may be altered.
  • In some cases, previously existing modifications in that file are overwritten or lost.

Expected result

  • Only the intended line or code block should change.
  • Existing unrelated edits in the same file should be preserved.

What is the expected behavior?

_No response_

Additional information

_No response_

View original on GitHub ↗

6 Comments

bryanxtong · 14 days ago

It always replace comments automatically. even edit the whole java file to one line. and a lot of edit errors and result in git restore to rollback change.

<img width="1923" height="1843" alt="Image" src="https://github.com/user-attachments/assets/44011caf-df36-4cc2-bc90-8fc1e5b7e7e6" />

rpelevin · 14 days ago

I would treat this as an edit transaction boundary, not just a diff-size bug.

A safe local-edit path should preserve three invariants:

  • the target context is unique before the patch applies;
  • the write set is limited to the intended span;
  • the post-edit diff is checked before the result is accepted.

If any invariant fails, the editor should stop before writing or leave the file unchanged and ask for confirmation.

The dangerous case is a fallback from minimal patch to whole-file rewrite without an explicit write boundary. That can turn a small requested change into a destructive operation against unrelated user work.

A useful regression would start with a dirty file, apply a one-line edit, and assert that pre-existing unrelated hunks remain byte-for-byte unchanged.

bryanxtong · 14 days ago

codex gives me why that happens as below: ( Bryan: the problem is that It alway tries to reuse the same error way of editing, and finally he tried python to make it work.)

The issue was my unsafe edit method: I used brittle replace/write operations instead of minimal patches, and I kept retrying the same failing approach. That caused whole-file rewrites and sometimes encoding/BOM/line-ending corruption on Windows, which could overwrite unrelated changes.

rpelevin · 14 days ago

That explanation matches the failure shape I was trying to isolate: the unsafe part is not just that the final diff was too large, but that the edit operation lost its transaction boundary and then kept retrying through the same broad writer.

A useful regression would separate three states:

  • unique target span found, minimal patch applied, post-edit diff reviewed;
  • target span ambiguous or missing, no file write attempted;
  • previous edit failed, retry must switch strategy or stop instead of rewriting the whole file.

The important invariant is that a failed local edit cannot widen authority on the next attempt. If the patch cannot prove the target span and write set, the safe result is no effect plus a clear diagnostic, not a full-file rewrite through a fallback path.

bryanxtong · 14 days ago

The problem is that even I have the rules in AGENTS.md, somtimes It does obey them and sticks to its own bad habits of editing.

<img width="1923" height="1843" alt="Image" src="https://github.com/user-attachments/assets/4c9c829d-ba37-4fa7-83fd-7bcc5e1d3990" />

Tuttotorna · 13 days ago

This looks like an edit-scope support boundary, not only a patching quality issue.

The runtime should separate:

edit request accepted

the resulting file mutation is supported by the requested scope

For a small targeted edit, the required support should include:

  • target context found
  • target context unique
  • mutation span bounded to the requested block
  • unrelated local changes preserved
  • resulting diff scope within the expected limit
  • line endings / formatting outside the target span unchanged unless explicitly requested

Minimal invariant:

AppliedDiffScope ⊆ RequestedEditScope

If the resulting diff modifies unrelated lines or overwrites pre-existing local changes, the edit should be treated as unsupported even if the file write succeeds.

Minimal verifier:

def verify_edit_scope(request, diff):
checks = {
"target_context_found": diff["target_context_found"],
"target_context_unique": diff["target_context_unique"],
"diff_inside_requested_scope": set(diff["changed_ranges"]).issubset(
set(request["allowed_ranges"])
),
"unrelated_local_changes_preserved": diff["unrelated_local_changes_preserved"],
"diff_size_within_limit": diff["changed_line_count"] <= request["max_changed_lines"],
"outside_formatting_unchanged": diff["outside_formatting_unchanged"],
}

failed = [name for name, ok in checks.items() if not ok]

if failed:
return {
"decision": "reject_or_request_confirmation",
"failed_support": failed
}

return {
"decision": "apply_edit",
"failed_support": []
}

Minimal negative controls:

[
{
"case": "small_edit_rewrites_unrelated_lines",
"request": {
"allowed_ranges": ["function_A_lines_20_24"],
"max_changed_lines": 5
},
"diff": {
"target_context_found": true,
"target_context_unique": true,
"changed_ranges": [
"function_A_lines_20_24",
"function_B_lines_80_120"
],
"unrelated_local_changes_preserved": true,
"changed_line_count": 45,
"outside_formatting_unchanged": false
},
"expected": "reject_or_request_confirmation"
},
{
"case": "target_context_ambiguous",
"request": {
"allowed_ranges": ["target_block"]
},
"diff": {
"target_context_found": true,
"target_context_unique": false,
"changed_ranges": ["target_block"],
"unrelated_local_changes_preserved": true,
"changed_line_count": 3,
"outside_formatting_unchanged": true
},
"expected": "reject_or_request_confirmation"
},
{
"case": "preexisting_local_change_overwritten",
"request": {
"allowed_ranges": ["function_A_lines_20_24"]
},
"diff": {
"target_context_found": true,
"target_context_unique": true,
"changed_ranges": ["function_A_lines_20_24"],
"unrelated_local_changes_preserved": false,
"changed_line_count": 4,
"outside_formatting_unchanged": true
},
"expected": "reject_or_request_confirmation"
}
]

Minimal positive control:

{
"case": "bounded_single_block_edit",
"request": {
"allowed_ranges": ["function_A_lines_20_24"],
"max_changed_lines": 5
},
"diff": {
"target_context_found": true,
"target_context_unique": true,
"changed_ranges": ["function_A_lines_20_24"],
"unrelated_local_changes_preserved": true,
"changed_line_count": 3,
"outside_formatting_unchanged": true
},
"expected": "apply_edit"
}

The key boundary is:

file write succeeded
does not imply
the edit respected the requested mutation scope.

If the patch engine cannot prove the target context is unique and the final diff remains inside the requested scope, it should stop and ask for confirmation instead of rewriting a larger part of the file.