Codex Duplicates Files Instead of Editing Existing Ones

Codex creates `utils.v2.ts` next to `utils.ts` instead of editing in place. The real cause is an apply_patch Update→Add fallback. Force in-place edits with AGENTS.md rules, git, and the right sandbox mode.

You asked Codex to add a validate() method to UserService.ts. It returned a clean diff, except the diff created UserServiceV2.ts with the method and left UserService.ts completely untouched. Now your imports still point to the old class, the new file is dead code, and you have to do the edit-in-place work yourself.

Fastest fix: reject the duplicate, then re-prompt with Edit src/UserService.ts in place. Do not create a new file. If you can't apply the patch cleanly, stop and explain why. That one clause clears most cases. If it keeps happening, the root cause is almost always Codex’s apply_patch falling back from *** Update File: to *** Add File: (a patch that didn’t apply, or a sandbox that blocks overwrites). The durable fix is an AGENTS.md rule plus the correct sandbox mode, both covered below.

Why this happens: the apply_patch Update→Add fallback

Codex edits files through one tool, apply_patch, and each hunk declares its intent in the patch header:

*** Begin Patch
*** Update File: src/UserService.ts   ← edits the file in place
*** Add File: src/UserServiceV2.ts     ← creates a brand-new file
*** Delete File: src/old.ts
*** End Patch

A duplicate appears when Codex meant to emit *** Update File: but produced *** Add File: instead. That happens for two broad reasons: the model chose to (your prompt, an unfamiliar file, or no rule telling it otherwise), or it was forced to (the update patch failed to apply, or the sandbox blocked an overwrite so it created a sibling instead). The table below maps the symptom to the bucket so you fix the right thing.

BucketTellFastest fix
Prompt wordingYour prompt contains “new version”, “v2”, “improved”, “refactored”Re-prompt with “edit in place”
Unfamiliar codeNew file uses simpler patterns than the original (raw function vs decorated class)Add an AGENTS.md in-place rule (Step 2)
No ruleAGENTS.md says nothing about file creationAdd an AGENTS.md in-place rule (Step 2)
Patch failedChat shows “patch failed” / “could not apply” before the new fileCommit first, ask for a smaller/targeted patch (Step 3)
Sandbox blocks overwriteNo-op edit on the file errors with a permission/write failureFix sandbox_mode (Step 5)
Genuinely two filesTask literally asked for a TS port or a variantNot a bug — name by purpose (Step 7)

Common causes

Ordered by hit rate, highest first.

1. Prompt used the word “new version” or “v2”

Codex takes file-naming language literally. “Write a new version of utils.ts” produces utils-new.ts; “make a v2 of this” produces utils.v2.ts. The hint came from your prompt, not from Codex deciding.

How to spot it: Search your prompt for “new”, “v2”, “improved”, “refactored”. Re-run with “edit utils.ts in place” and the duplicate goes away.

2. Original file had unfamiliar patterns Codex didn’t want to touch

utils.ts uses a custom decorator or a generic shape the model is less sure about. It hedges by leaving the original intact and writing a new file using patterns it understands.

How to spot it: Check whether the new file uses simpler patterns than the old (e.g., raw function instead of decorated class). That asymmetry is the tell.

3. AGENTS.md was silent on file-creation rules

With no explicit rule, Codex leans toward “create new” when changes are large, because an *** Add File: is reversible (delete it) while an *** Update File: that mangles the original may not be.

How to spot it: grep -i "in place\|do not create" AGENTS.md — empty means no rule. Note that Codex reads AGENTS.md from the git root down to your current directory (and ~/.codex/AGENTS.md globally), concatenating them, so check every level.

4. The update patch was too large to apply cleanly

Codex tried an *** Update File: edit, the patch didn’t apply (the context lines didn’t match the file on disk), and it fell back to writing the whole thing as an *** Add File:. The fallback isn’t always surfaced clearly.

How to spot it: Check the transcript for “patch failed”, “could not apply”, or “context did not match” messages immediately before the new file appears. If present, this is the trigger.

5. Sandbox mode blocked overwriting the existing file

Codex runs commands inside a sandbox. The default is workspace-write (read everything, write inside the workspace). But if the agent is effectively read-only — a misconfigured sandbox_mode, a path outside sandbox_workspace_write.writable_roots, or the known Windows desktop bug where apply_patch can add files but cannot update existing ones (openai/codex issue #25860) — it works around the block by creating a parallel file.

How to spot it: Try a no-op edit on utils.ts (add a trailing space). If it fails with a permission or write error, the sandbox is blocking in-place writes. On the desktop app, watch for an apply_patch that succeeds on Add but returns exit code 1 with no output on Update.

6. The task implied parallel implementations were needed

“Add a TypeScript version of utils.js” or “create a strict-mode variant” both legitimately ask for two files. Make sure you didn’t mean “convert in place.”

How to spot it: Re-read the original task. If “in place” or “replace” isn’t in it, the duplication may be on-spec.

Shortest path to fix

Ordered by ROI. Step 1 alone clears most cases.

Step 1: Reject the duplicate, re-prompt with explicit in-place language

Delete the duplicate, keep the original, and send:

Edit `src/utils.ts` IN PLACE.
- Use apply_patch with *** Update File:, not *** Add File:.
- Do not create a new file.
- Do not rename the file.
- Apply the change directly to the existing file.
- If the patch won't apply cleanly, stop and explain why. Do not create an alternative file.

Naming the *** Update File: mechanism and the “stop and explain why” clause both matter: together they remove the “create a duplicate” escape hatch and replace it with a clean failure you can act on.

Step 2: Add a permanent rule to AGENTS.md

In your repo root, add or update AGENTS.md. Codex reads this on every run, concatenating from the git root down to your working directory (and ~/.codex/AGENTS.md if you want it global), with later files winning on conflict; the combined instruction chain is capped at 32 KiB.

## File creation policy

- ALWAYS edit existing files in place (apply_patch `*** Update File:`).
- DO NOT create variant files: no `*-new.ts`, `*.v2.ts`, `*_copy.ts`, `*-improved.ts`, `*-refactored.ts`.
- New files (`*** Add File:`) are only allowed when:
  - The user explicitly says "create a new file"
  - A new feature genuinely needs a new module
  - You're adding a test file for an existing module (e.g., `utils.test.ts`)
- If an in-place edit is too risky or the patch won't apply, stop and ask before creating an alternative.

The rule sticks across sessions. (Tip: codex won’t overwrite an existing AGENTS.md via /init, so edit the file directly.)

Step 3: Use git as the safety net, not file duplication

Codex hedges with duplicates because it is worried about losing the original. Replace that hedge with git:

# Before risky edits, commit a checkpoint
git add -A && git commit -m "checkpoint before Codex edit"

# Let Codex edit in place
# ... if it goes wrong:
git restore src/utils.ts  # one-liner revert

Then in your prompt:

The repo is committed at the current state. If you make a mistake,
I can `git restore` instantly. Prefer in-place edits — git is the
safety net, not duplicate files.

If the duplication came from a failed patch (bucket “patch failed”), also ask Codex to make a smaller, targeted change: “Edit only the validate method; leave the rest of the file byte-for-byte unchanged.” Smaller hunks apply more reliably and rarely trigger the Add fallback.

Step 4: Confirm the sandbox can actually write in place

If a no-op edit fails (Step / bucket 5), check your sandbox mode rather than fighting the symptom. The default workspace-write should let Codex update files inside the workspace.

# See the active mode and writable roots
codex --help          # shows --sandbox and approval flags
# Run a session that can write the workspace:
codex --sandbox workspace-write

Or set it in ~/.codex/config.toml:

sandbox_mode = "workspace-write"          # read-only | workspace-write | danger-full-access
approval_policy = "on-request"

For the known Windows desktop app bug where apply_patch can add but not update files, switching the session to full access is the documented workaround (use it only when you trust the task):

codex --sandbox danger-full-access

Verify the directory you’re editing is covered by sandbox_workspace_write.writable_roots; an edit target outside those roots reads as read-only and pushes Codex toward a sibling file.

Step 5: Clean up existing duplicates

If your repo already has *-v2, *-new, *-copy files from past sessions, list them and consolidate:

# Find probable variant files
find src -type f \( \
  -name "*-new.*" -o \
  -name "*.v2.*" -o \
  -name "*-v2.*" -o \
  -name "*_copy.*" -o \
  -name "*-copy.*" -o \
  -name "*-refactored.*" \
\) | sort

For each pair, decide which to keep:

| Old file        | New file           | Action                            |
|---|---|---|
| utils.ts        | utils-new.ts       | Move utils-new content → utils.ts, delete utils-new |
| UserService.ts  | UserServiceV2.ts   | If imports use V2, rename V2 → original. Delete the unreferenced one. |

Then update imports across the codebase (your IDE’s “rename symbol” handles most of this).

Step 6: Block variant filenames at PR review

In your CI, fail any PR that adds a probable variant filename:

# .github/workflows/no-variant-files.yml
- name: Block variant filenames
  run: |
    if git diff --name-only --diff-filter=A origin/main...HEAD | grep -E '(\-new|\.v2\.|\-v2\.|_copy\.|\-copy\.|\-refactored)' ; then
      echo "Variant filenames are not allowed. Edit in place."
      exit 1
    fi

Step 7: For genuine A/B implementations, name them by purpose, not by version

When two implementations really do need to coexist (legacy + new API), name them by what they do, not by version:

auth/oauth.ts        + auth/magic-link.ts          ← good
api/users-v1.ts      + api/users-v2.ts             ← acceptable if both are public versions
api/utils.ts         + api/utils-new.ts            ← never

This convention keeps Codex from interpreting a future edit as “create a new variant.”

How to confirm it’s fixed

  1. Re-run the original task with the in-place prompt and AGENTS.md rule in place.
  2. Inspect the diff: the patch header should read *** Update File: src/utils.ts, not *** Add File:.
  3. Confirm no new sibling file was created: git status --short shows M src/utils.ts, not a new A line.
  4. Confirm your imports still resolve and tests pass — the edit landed in the file your code actually imports.

Prevention

  • Bake “edit in place” into AGENTS.md once and forget it — Codex reads it every run.
  • Never use the words “new version”, “v2”, “improved”, “refactored” in prompts unless you actually want a separate file.
  • Commit a checkpoint before risky edits so Codex doesn’t hedge against irreversible damage.
  • Keep sandbox_mode = "workspace-write" (or grant the right writable_roots) so in-place updates don’t get blocked into siblings.
  • Block variant filenames at PR review with a CI check; it catches what humans miss.
  • Audit existing variant files quarterly — they accumulate silently and create import-target confusion.
  • When two implementations really must coexist, name them by purpose (oauth + magic-link), not by version.

FAQ

Why does Codex create UserServiceV2.ts instead of editing UserService.ts? Because its apply_patch call emitted an *** Add File: hunk instead of *** Update File:. That’s triggered by version-y prompt wording, an unfamiliar original file, a missing in-place rule in AGENTS.md, a patch that failed to apply, or a sandbox that blocked the overwrite. Use the bucket table above to identify which.

How do I force Codex to always edit in place? Add a “File creation policy” section to AGENTS.md (Step 2) that mandates *** Update File: and forbids variant filenames, and start tasks with “edit in place, do not create a new file.” The AGENTS.md rule applies to every run; the prompt line handles the current one.

Codex says the patch failed and then made a new file. What do I do? That’s the Update→Add fallback. Commit a checkpoint, then ask for a smaller, targeted change (“edit only this function, leave the rest unchanged”). Smaller hunks match the file’s context lines more reliably, so the update applies instead of falling back to Add.

On Windows the desktop app keeps adding files but won’t update them. Is that a bug? Yes — a known issue (openai/codex #25860) where apply_patch can add files but fails to update existing ones, often returning exit code 1 with no output. Running the session with --sandbox danger-full-access is the documented workaround; the same flow works correctly under WSL.

Is it ever correct for Codex to create a second file? Yes. A real TypeScript port of a JS file, a genuinely new module, a test file, or two public API versions (users-v1 / users-v2) all warrant separate files. The problem is only version-suffix siblings (-new, .v2, -copy) that shadow a file you wanted edited in place.

Tags: #Codex #Coding agent #Troubleshooting #Debug #Duplicates files