You asked Claude Code or Cursor to refactor a component. Instead of editing the file, it created UserList2.tsx / UserListNew.tsx / user-list-v2.tsx and wrote the changes there. Worse, sometimes it imports the new file while leaving the old one in place — two copies live in parallel, with half a bug fixed in the new file and the other half still in the old one.
This guide gives one cleanup path: diff the two files, merge the good parts into the surviving file, delete the duplicate, and fix imports. Then add a CLAUDE.md rule so it doesn’t happen again.
Common causes
Ordered by hit rate.
1. Agent didn’t read the original and wrote from imagination
The most common cause. You said “refactor UserList”; the agent’s grep or file search missed the exact match (path alias, hyphenated filename, the component lives under features/ not components/), so it assumed the file didn’t exist and created a new one.
> Tool: write_file
path: src/components/UserList2.tsx
reason: "Creating a new improved version of the UserList component."
How to spot it: The agent’s log or chat transcript says “creating new file” when you intended an edit; or git status shows files like UserList2.tsx / *New.tsx / *v2.tsx.
2. Multi-step agent forgot it already created the file
Aider or Codex in a long session created lib/utils/format.ts early on. A few steps later you asked for “a formatting helper”; it created src/helpers/format.ts again. Same signature, slightly different implementation.
How to spot it: rg "export function formatDate" -t ts returns more than one hit across different paths.
3. Naming convention drifted mid-conversation
You called it userService in the first half; the agent later wrote UserService / user-service. When import resolution failed, it created a new file. On case-insensitive filesystems (macOS by default) this is invisible locally and explodes on Linux CI.
How to spot it: git ls-files | sort -f | uniq -di lists names that only differ by case.
4. Agent makes a “safe copy” to avoid breaking the original
Some agents (especially those repeatedly told “be careful with committed code”) default to copying before changing. To convert a class component to hooks it creates UserListHooks.tsx instead of replacing UserList.tsx.
How to spot it: The new file is a “style-upgraded” version of the old one with a near-identical public API.
5. Parallel agents or branches built the same thing
You had Cursor write analytics/track.ts in the morning; in the afternoon you had Claude Code write lib/analytics.ts on another branch. They both land in the merge.
How to spot it: git log --diff-filter=A --since="1 week" -- "*analytics*" shows multiple “new file” commits in the same week.
6. Agent doesn’t know your directory conventions
The prompt didn’t say “src/components/ is UI, src/features/<name>/ is business logic,” so the agent puts a UI component under features/ while the previous UI version still sits in components/.
How to spot it: Two functionally-equivalent files exist under different top-level directories.
Shortest path to fix
Step 1: List every suspect duplicate
# Files with number / New / v2 / copy suffixes
git status --short | grep -E '(2|3|New|new|v2|copy|Copy)\.(ts|tsx|js|jsx|py)$'
# New files in the last week whose basename matches an existing file
git log --diff-filter=A --since="1 week" --name-only --pretty=format: \
| sort -u | awk -F/ '{print $NF}' | sort | uniq -d
Write the path pairs down before doing anything else.
Step 2: Diff each pair and pick the survivor
diff -u src/components/UserList.tsx src/components/UserList2.tsx
# or side by side
git diff --no-index src/components/UserList.tsx src/components/UserList2.tsx
Decide using this table:
| Situation | Keep |
|---|---|
| New file is a complete rewrite; nothing imports the old one | Keep new, delete old |
| Old file is still imported; new file has the real fix | Cherry-pick the fix into the old file, delete new |
| Each file fixed a different bug | Manually merge into the old path, delete new |
| Files are identical | Delete either |
Always keep the path with more imports — fewer import edits means fewer ways to break things.
Step 3: Merge the good content into the surviving file
Let the agent cherry-pick rather than rewrite — much safer:
Prompt: Compare src/components/UserList.tsx and src/components/UserList2.tsx.
List only the logic changes present in UserList2 but missing from UserList.
Apply only those changes to UserList.tsx — no style, ordering, or rename edits.
Have it output the diff plan first; approve before it writes.
Step 4: Delete the duplicate and fix imports
git rm src/components/UserList2.tsx
# Find any lingering references
rg "UserList2|user-list-v2" --type ts --type tsx
Update each remaining import to the correct path. In a TypeScript project, tsc --noEmit flags every broken import in one shot:
npx tsc --noEmit
Step 5: Run tests and build to verify
npm test
npm run build
Pay special attention to case-insensitive filesystem traps (works on macOS, breaks on Linux CI) — run git ls-files | sort -f | uniq -di one more time.
Prevention
- Say “edit
src/components/UserList.tsx” in the prompt, not “create an improved version”; use@-mentions for explicit paths - Add to CLAUDE.md / .cursorrules / AGENTS.md: “never create files with number / New / v2 / copy suffixes; to replace a file, edit it in place”
- Pre-commit hook to reject filenames like
*2.tsx,*New.tsx,*Copy.tsx git diff --statreview every agent round; any unfamiliar new-file path is a stop sign/clearlong sessions or start fresh to avoid the “forgot I already created it” failure mode- TypeScript: set
forceConsistentCasingInFileNames: trueand runtsc --noEmitin pre-commit to catch case duplicates before they merge
Related
- Claude Code edited the wrong file
- AI removed working logic during refactor
- Rolling back AI code changes
- AI pre-commit review workflow
- AI dependency upgrade workflow
Tags: #AI coding #Debug #Troubleshooting