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, while leaving 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.
This pattern is Codex hedging: when the original file looks “complicated” or it’s not 100% sure what to keep, it sidesteps the merge by creating a sibling. The fix is explicit in-place editing rules, AGENTS.md guardrails, and using git for safety instead of file duplication.
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 Codex’s training didn’t cover well. 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 / CLAUDE.md was silent on file creation rules
With no explicit rule, Codex’s default leans toward “create new” when changes are large, because creating a file is reversible (delete it) while overwriting may not be (depending on harness).
How to spot it: cat AGENTS.md | grep -i "in place\|do not create" — empty means no rule.
4. Diff against original was too large to apply cleanly
Codex tried an in-place edit, the patch failed (line offsets didn’t match), and it fell back to writing the whole thing as a new file. The fallback isn’t always logged.
How to spot it: Check the chat for “patch failed” or “could not apply” messages preceding the new file. If present, this is the trigger.
5. Permission scope blocked overwriting the existing file
Some Codex sandbox configs default to “create allowed, overwrite denied.” The agent worked around the limit by creating a parallel file.
How to spot it: Try a no-op edit on utils.ts (add a space). If it fails with a permission error, the harness is blocking writes.
6. 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 70% of 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.
- Do not create a new file.
- Do not rename the file.
- Apply the change directly to the existing file.
- If the file is too complex to edit cleanly, stop and explain why.
The “stop and explain why” clause is critical — it gives Codex an exit other than “create a duplicate” when it’s stuck.
Step 2: Add a permanent rule to AGENTS.md
In your repo root, add or update AGENTS.md:
## File creation policy
- ALWAYS edit existing files in place.
- DO NOT create variant files: no `*-new.ts`, `*.v2.ts`, `*_copy.ts`, `*-improved.ts`, `*-refactored.ts`.
- New files 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, stop and ask before creating an alternative.
Codex reads this on every task; the rule sticks across sessions.
Step 3: Use git as the safety net, not file duplication
Codex hedges with duplicates because it’s 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.
Step 4: 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 5: 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 6: 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 prevents Codex from interpreting any future edit as “create a new variant.”
Prevention
- Bake “edit in place” into AGENTS.md once and forget it — Codex reads it every task
- 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
- Block variant filenames at PR review with a CI check; lint 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
Related
- Codex ignores project structure
- Codex patch conflicts with existing code
- Codex misses project-specific conventions
- Codex beginner guide
- Codex code review workflow
- Codex vs Claude Code
Tags: #Codex #Coding agent #Troubleshooting #Debug #Duplicates files