Cursor Generates Duplicate Logic Instead of Reusing Helpers

Cursor wrote a new formatDate but src/utils already has one. The existing helper never reached retrieval — here is how to make Cursor grep first and reuse it.

You ask Composer for a date-formatting helper. It hands you function formatDate(d) {...}. Open src/utils/date.ts: a mature formatDate already lives there with timezone and locale handling. The model isn’t lazy. Its retrieval just never surfaced the existing helper, so it “honestly” concluded the repo had none and rewrote one.

Fastest fix: before asking for the helper, tell the agent to search first. Paste this one line at the top of your prompt:

First run a codebase search and grep for existing "date format" helpers, list what you find with file paths, and reuse one if it fits. Only write new code if nothing suitable exists.

That alone stops most duplicates in a single turn. The rest of this guide makes reuse the default so you don’t have to ask every time.

Why this happens (the 30-second version)

Cursor doesn’t read your whole repo on every request. For each turn it retrieves a limited set of chunks using two mechanisms: semantic search over a vector index of your codebase, plus exact-match grep (Cursor ships its own Instant Grep engine, which it says outperforms ripgrep on large codebases). The model then reasons over only what came back. If your formatDate lived in a low-ranked path, used different naming, or the agent simply skipped the search step, the helper was invisible at write time. As of June 2026 Cursor reports that combining semantic search with grep gives roughly a 12.5% accuracy lift on codebase questions versus grep alone, with the gap largest on repos over 1,000 files — so the failure mode concentrates in big codebases.

Which bucket are you in

Run these checks before changing anything. They map directly to the fixes below.

Symptom you observeLikely causeFastest fix
Agent wrote new code without any search step in its tool chainAgent didn’t search before writingStep 1 (force search)
rg "function formatDate" finds the existing helper, agent didn’tHelper outranked / buried in retrievalSteps 2-3 (manifest + rules)
Your repo names it renderDate / displayDate, agent made formatDateNaming-style mismatchSteps 1 + 6
rg finds 2+ existing copies alreadyRepo already has duplicatesStep 5 (dedupe sweep)
You used Cmd+K, not Composer/AgentCmd+K has no repo-wide searchSwitch to Composer/Agent

Common causes

1. Existing helper not in retrieval context

Repo has 10k files; retrieval pulls a limited top-K set of chunks. src/utils/date.ts didn’t match the prompt’s keywords or embedding well enough to make the cut.

How to judge: ask “List every file you read for this.” If src/utils/date.ts isn’t there, it wasn’t seen.

2. Helper buried in a non-obvious path

packages/feature-a/internal/helpers/date.ts ranks low, and the generic filename date.ts repeats everywhere, so nothing about it stands out.

How to judge: rg "function formatDate" yourself. Count hits and note locations.

3. Naming style mismatch

Your repo uses toLocaleDateString / renderDate / displayDate; the model defaults to formatDate. Semantic search partially bridges the gap, but the ranking stays low and exact grep for formatDate misses entirely.

How to judge: list your repo’s actual naming next to the model’s proposal and eyeball the style gap.

4. No centralized utility index

No src/utils/README.md, no rules file listing the helpers. The model has to rediscover the layout from scratch on every turn.

How to judge: ls src/utils/ — is there a README.md or an index.ts barrel as an entry point?

5. The repo already has duplicates

The model isn’t only producing duplicates; the repo already has several. packages/web has one formatDate, packages/admin has another, the model sees one and adds a third.

How to judge: rg "function formatDate" --type ts — count occurrences across the whole repo.

6. Agent didn’t grep before writing

Agent mode can call codebase search and grep, but the model doesn’t always do it. It writes first and searches never.

How to judge: open the message’s tool-call chain (the collapsed “thought”/tools panel under the agent’s reply). If there’s no search/grep/read step before the edit, it wrote blind.

Before you start

  • Identify the entry point: Composer/Agent, Cmd+K, or chat. Cmd+K (inline edit) operates on the current selection and does not run repo-wide search, so it will always duplicate. Use Composer/Agent for anything that should reuse shared code.
  • Commit first so later helper consolidation stays tracked and you can roll back cleanly.
  • Note your Cursor version and active model. As of June 2026 the agent picker includes Claude Sonnet 4.6, Claude Opus 4.7, GPT-5.5, Gemini 3.1 Pro, and Cursor’s own Composer 2.5. Reasoning-heavy models (e.g. Opus 4.7) tend to search before writing more reliably than faster models; if duplicates persist, switch the model and retry the same prompt.

Info to collect

  • Full text of the new helper Composer produced.
  • Paths and implementations of existing similar helpers in your repo.
  • A screenshot of the message’s tool-call chain.
  • Your rules files: .cursor/rules/*.mdc, any root AGENTS.md, and legacy .cursorrules if present (check for a “reuse first” clause).
  • The layout of src/utils/ or your equivalent.

Shortest fix path

Order is “fix this instance, then fix systemic discoverability.”

Step 1: Make the model search before writing

Use this as a reusable prompt template:

Before writing new code:
1. Run a codebase search AND grep for existing functions related to "date formatting" / "format date" / "render date".
2. List what you found with file paths.
3. If a suitable one exists, reuse it and explain why.
4. Only if nothing suitable exists, write new code — and propose a location.

In Agent mode you can also tell it to “use the Explore subagent first” — Cursor can spawn a separate Explore agent that runs many parallel searches in its own context window and returns only the relevant findings, which is ideal for “does this already exist?” questions on large repos.

Step 2: A utility manifest the model can read

Add src/utils/README.md as a one-glance index:

# Utilities

| Function | File | Purpose |
|---|---|---|
| formatDate | date.ts | Locale-aware date formatting |
| formatCurrency | money.ts | Currency formatting with i18n |
| parseInvoice | invoice.ts | Parse invoice payloads |
| isValidEmail | validation.ts | RFC 5322 email check |

Then reference it with @src/utils/README.md in relevant Composer prompts so the manifest is guaranteed in context, instead of hoping retrieval surfaces it.

Step 3: A “reuse first” rule — use the current rules format

Important change as of 2026: the single root .cursorrules file is legacy and deprecated, and it is not loaded reliably in Agent mode. Cursor’s current formats are a root AGENTS.md (plain markdown) or, for richer scoping, project rules under .cursor/rules/*.mdc. If you still have a .cursorrules that “isn’t working,” migrating it is usually the fix.

Simplest path — root AGENTS.md:

# Project conventions

- Before writing a new utility function, search src/utils/ and packages/shared/ for an existing implementation. Reuse it if one fits.
- Common helpers:
  - src/utils/date.ts (date / time formatting)
  - src/utils/money.ts (currency)
  - src/utils/validation.ts (input validation)
- New helpers go in src/utils/ and must be added to src/utils/README.md.
- Do not inline helpers inside component files; extract to src/utils/.

Richer path — a project rule at .cursor/rules/reuse-helpers.mdc. The frontmatter keys are description, globs, and alwaysApply, and the combination determines the rule type (Always / Auto Attached / Agent Requested / Manual):

---
description: Reuse existing utility helpers instead of writing new ones
alwaysApply: true
---

- Before writing a new utility function, search src/utils/ and packages/shared/ first.
- Reuse an existing helper if one fits; only create new code when nothing matches.
- New helpers go in src/utils/ and must be listed in src/utils/README.md.

Keep an alwaysApply: true rule short (under ~200 words) — it is sent on every request, so token cost adds up. You can run /create-rule in chat to scaffold one. (Plain .md files in .cursor/rules/ are ignored because they lack frontmatter — the file must be .mdc.)

Step 4: Push back the moment you see duplication

You wrote a new `formatDate` function, but src/utils/date.ts already has one with timezone handling.
Replace your implementation with `import { formatDate } from "@/utils/date"`.
Find and fix any other helpers you duplicated the same way.

Step 5: Repo-wide dedupe sweep

# List all function names and count duplicates
rg "^(export )?(async )?function (\w+)" --type ts -o -r '$3' | sort | uniq -c | sort -rn | head -30

# Find formatDate copies specifically
rg "function formatDate" --type ts

Consolidate to a single source and update the call sites. After this, future agent runs have one anchor to find instead of three.

Step 6: Give important helpers unique names

formatDate is too generic — it collides with the model’s default name and with every other date.ts. Rename domain helpers with a signal: formatInvoiceDate, formatRelativeDate, formatLocaleDate. Both grep precision and semantic ranking improve, and the model is far less likely to rewrite a distinctly named helper.

How to confirm it’s fixed

  • Rerun the original prompt. In the tool-call chain you should now see a search/grep step before any edit, and the reply should name the existing helper instead of writing a new one.
  • The diff has noticeably fewer new helper definitions.
  • Ask “List every file you read.” The existing helper’s path should appear.
  • A teammate running the same prompt sees the same behavior — that confirms the rule is in shared config (AGENTS.md or .cursor/rules/), not just your local chat.

If it still fails

  • Shrink the prompt to just “is there an existing date-format helper? Search and tell me the path.” If it still can’t find it, the index is stale — open Cursor Settings → Indexing & Docs and re-index the codebase, then retry.
  • Confirm the file isn’t excluded: check .cursorignore / .cursorindexignore and your .gitignore, since ignored paths don’t get indexed.
  • If a rule isn’t taking effect, check it’s in a .mdc file (not .md) under .cursor/rules/, and that alwaysApply or the glob actually matches; legacy .cursorrules in Agent mode is the usual culprit.
  • Roll back the latest rule change to isolate which rule was doing the work.
  • Search forum.cursor.com for “duplicate helper composer” and, if it’s a real bug, post to Bug Reports with your prompt and tool-call screenshots. Attach agent logs from the Output panel (View → Output → select “Cursor”).

Prevention

  • One utils/ per package plus a README manifest — give the model an explicit entry point.
  • A “reuse first” rule in AGENTS.md or .cursor/rules/*.mdc that lists your main helper paths (not in legacy .cursorrules).
  • Name important helpers with domain signal (formatInvoiceDate beats formatDate).
  • PR review checklist item: “Is this reusing the existing helper?” Block duplication at review.
  • Quarterly utility dedupe sweep: merge near-duplicates and update the README.

FAQ

Why does the agent reuse helpers some days and duplicate them other days? Retrieval is probabilistic — which chunks rank into the limited context depends on your exact wording, the active model, and whether the agent chose to grep. Make reuse deterministic with a manifest you @-reference and an always-on rule, instead of relying on retrieval landing the right chunk.

Does .cursorrules still work in 2026? The root .cursorrules file is legacy and deprecated. It still loads in some modes but behaves unreliably in Agent mode. Move your conventions to a root AGENTS.md or to .cursor/rules/*.mdc project rules, which load consistently across Chat, Composer, and Agent.

Should I use AGENTS.md or .cursor/rules/*.mdc? Use AGENTS.md for simple, repo-wide conventions in one readable file. Use .cursor/rules/*.mdc when you need scoping — e.g. a rule that only attaches when editing certain globs, or one the agent pulls in on demand by description. They coexist; you don’t have to pick only one.

The helper exists but the agent says it can’t find it. What now? Your index is probably stale or the file is ignored. Re-index from Cursor Settings → Indexing & Docs, and confirm the path isn’t in .cursorignore, .cursorindexignore, or .gitignore. Then @-mention the file directly to bypass retrieval entirely.

Does switching models help? Sometimes. Reasoning-heavier models (Opus 4.7) tend to search before writing more often than faster ones. But model choice is a band-aid; the durable fix is the manifest plus a “reuse first” rule so behavior doesn’t depend on which model is active.

Tags: #Troubleshooting #Cursor #Debug #Duplicate logic