Quick answer: When an agent adds a "small helper" that already exists elsewhere in the repo, you don't just have a duplicate file. You now have two functions that look identical today but will drift apart the first time someone fixes a bug in one of them. Treat the discovery of an existing utility as a hard stop: reuse it, or deliberately consolidate, never silently fork it.

I found the pattern on a routine review of a checkout change. The agent had introduced formatMoneyCents() in a new pricing.ts. It was well written, it had a unit test, and the feature worked. The problem was that packages/currency/src/format.ts already exported formatCurrency(), which did the same job with one extra responsibility: it applied the rounding rules that finance had signed off on after a real incident with fractional cents. The new helper rounded half-up instead of half-even. Nothing failed in the pull request. It would have failed quietly, months later, on a specific refund total.

Why agents do this so often

An agent does not have a stable, searchable map of your entire codebase in its head the way a tenured engineer does. It works from the context it was given, and that context is usually the files in the current task plus whatever retrieval surfaced. A few forces make duplication almost inevitable:

  • Local context bias. If the relevant utility is three packages away and nothing in the prompt points to it, the agent solves the problem inside the files it can see.
  • Name mismatch. The existing helper may be called formatCurrency while the agent thinks in terms of "money cents," so even a fuzzy search misses it.
  • Reward for completion. A self-contained helper makes the current change pass without touching shared code. That is the lowest-risk path to a green build, so the agent takes it.
  • Barrel exports hide the source. When everything is re-exported through an index, the agent may not realize the function it needs is already on the public surface.

None of these is a model failure you can prompt away with a single line. It is a retrieval and codebase-ownership problem, and the fix is a combination of conventions and a review gate.

What the duplication actually costs

The cost is not disk space or a few extra lines. It is the loss of a single place to change behavior. The table below is how I think about the trade-off when I find a duplicate.

FactorReusing the existing utilityAdding a new helper
Bug fixesEvery caller gets the fix at onceMust be applied in two places; one will be missed
Rounding/domain rulesStays consistent with the approved behaviorCan silently diverge under a different default
Bundle sizeOne implementationDead or duplicated code ships until tree-shaking proves otherwise
Review surfaceChange is localized to call sitesA new public-looking function needs its own tests and docs
Short-term riskSlightly higher, since shared code is touchedNear zero, which is why agents prefer it

How to detect it before merge

I rely on a cheap, repeatable check rather than willpower. Before accepting any new helper, I ask the agent to justify why an existing one cannot be used, and I make the search concrete.

# Search by behavior, not just the name the agent invented
rg -n "round|currency|money|cents" packages src --glob '*.ts'

# List everything already exported on the public surface
rg -n "export .*format" packages/currency/src

If your editor is agent-driven, fold this into the prompt as an explicit instruction before code is written, not after:

Before writing any new helper, search the repo for an existing
function that does this. If one exists, reuse it and note its path.
Only create a new function if the existing one cannot satisfy the
requirement, and explain why in one sentence.

For shared domains such as money, dates, and identifiers, I go further and add a lint or architecture rule that marks those packages as the only allowed home for that logic. A dependency rule or an import boundary turns "please remember" into something the CI enforces.

When a second copy is legitimate

It is rare, but duplication is defensible when the two functions encode genuinely different policies. A reporting module that deliberately rounds for display while the ledger keeps exact precision may want separate, explicitly named functions. The key word is deliberate. The difference should be visible in the names, documented at both sites, and covered by a test that pins the divergent behavior. If you cannot articulate the policy difference in one sentence, you do not have two functions; you have one function and a future bug.

When you find one, consolidate forward

Do not just leave a comment. Replace the new call sites with the shared utility, delete the duplicate, and run the full suite. If the shared helper is missing a feature the new one had, extend the shared one behind its existing tests rather than keeping both. That keeps the domain rule in one place and makes the next agent more likely to find the right function, because there is now only one name to find.

FAQ

Isn't a small local helper safer than touching shared code?

It feels safer in the moment because it avoids a broad change, but it externalizes the risk. Shared code carries the approved domain behavior; a fresh local copy can diverge without any test noticing. Safety here means reusing the reviewed utility, even though that touches a shared module.

How do I make the agent search before it writes?

Make retrieval an explicit first step in the prompt and require the agent to report the path of any existing function it considered. Pair that with barrel exports that put shared utilities on a predictable public surface so the search has somewhere obvious to land.

What if the existing utility is badly designed?

Improve that one utility rather than forking it. Add the missing capability behind its existing tests, update call sites, and remove the would-be duplicate. Refactoring one shared function is still cheaper than maintaining two that drift.

Can linting really prevent this?

It cannot recognize every semantic duplicate, but it can protect high-value domains. Import boundaries and module rules can force money, time, and identifier logic to live in a single package, which eliminates the most damaging class of duplicated helpers.

This is part of the AI Coding Agent Guide — start there for the full picture of how agent-generated code drifts and how to keep it reviewable.