AI coding agents do not usually duplicate utilities because they are lazy.
They duplicate utilities because the repo did not make reuse cheaper than invention.
That is the uncomfortable little operational truth.
It sounds like a tooling problem.
In practice, it is a lookup problem.
The agent gets a task.
It sees the nearby file.
It sees the failing test.
It sees a pattern that looks easy to solve.
Then it creates formatDateSafe, normalizeCurrencyInput, buildRetryPolicy, or some other suspiciously familiar helper.
The tests pass.
The diff looks reasonable.
The reviewer feels a tiny itch.
Two weeks later, the same repo now has four versions of the same utility.
Congratulations, the codebase has opened a small helper franchise.
This field note is about preventing that.
Not with a grand AI architecture.
Not with another 900-line rules file.
Not with a magical MCP server that promises to understand your entire company.
The fix I trust in 2026 is smaller.
Make repo lookup a required pre-edit step.
Make the agent return the paths it checked.
Make duplicate utility failures visible in a tiny log.
Only then decide whether memory, rules, skills, or MCP deserve to enter the room.
The failure pattern I keep seeing
The failure starts with a prompt that sounds harmless.
Add CSV export support to the report page.
The agent scans the target component.
It finds rows of table data.
It writes a local toCsv helper.
It adds a download button.
It runs the test.
It passes.
But the repo already had packages/shared/export/csv.ts.
That utility already handled escaping.
It already handled line endings.
It already handled browser download names.
The new helper only handled the happy path.
The bug is not that the agent wrote code.
The bug is that the agent was rewarded before it proved the code did not already exist.
This is why “tests passed” is not enough for AI-assisted work.
Tests can prove the new behavior works.
They do not prove the new abstraction belongs in the repo.
That second question needs a workflow.
The operating rule
My current rule is simple.
No new utility until lookup evidence exists.
The agent must show where it searched.
The agent must show what it found.
The agent must classify the result.
The agent must explain why reuse, extension, or a new helper is the right move.
If it cannot do that in a few lines, the task is not ready for editing.
This rule feels boring.
That is a feature.
Boring rules survive busy review days.
Fancy rules usually get admired once and ignored by Thursday.
Why this got worse in 2026
AI coding agents are better at local completion now.
They are also better at making a plausible local helper without asking for the whole repo.
That is useful for speed.
It is dangerous for shared utilities.
Claude Code, Codex, Cursor, and similar agents can operate across files.
They can call tools.
They can use memory.
They can connect to MCP servers.
They can run tests.
But none of those abilities automatically create repo discipline.
Anthropic’s Claude Code memory docs make a useful distinction here.
CLAUDE.md is persistent project instruction.
Auto memory is accumulated learning.
Both are loaded as context, not as hard enforcement.
That matters.
Writing “reuse existing utilities” into memory is helpful.
It is not a guarantee.
The agent still needs a concrete lookup step.
The reviewer still needs evidence.
The repo still needs a cheap path to the existing helper.
The repo lookup flow
Here is the flow I use before allowing a new utility.
It is intentionally command-line friendly.
It works without a new server.
It works in a half-documented repo.
It works when the agent is not sure what to search for.
Step 1: name the capability, not the file.
Write the behavior in plain language.
Example: “convert report rows into CSV.”
Example: “normalize user-entered money strings.”
Example: “retry failed HTTP calls with jitter.”
Example: “resolve a workspace-relative path.”
Step 2: generate three search terms.
Use one product term.
Use one implementation term.
Use one domain synonym.
For CSV export, search csv, export, and escape.
For currency parsing, search currency, money, and amount.
For retry logic, search retry, backoff, and jitter.
Step 3: search names first.
Use rg -n "csv|export|escape" src packages lib.
Use the repo’s actual directories.
Do not search the entire home folder like a tiny chaos machine.
Step 4: search exports next.
Use rg -n "export .*csv|function .*csv|const .*csv" src packages lib.
Use language-specific patterns when the repo gives you a clue.
Step 5: search tests.
Use rg -n "csv|export|escape" test tests __tests__ spec.
Existing tests often reveal utilities that are not obvious from filenames.
Step 6: search docs and rules.
Use rg -n "csv|export|escape" README.md docs .claude CLAUDE.md AGENTS.md.
This is where coding standards and “use this helper” notes often live.
Step 7: inspect only the top candidates.
Open the candidate files.
Read the public function names.
Read the tests.
Do not let the agent wander for 20 minutes.
Step 8: classify the result.
Use one of four labels.
reuse: existing utility works as-is.
extend: existing utility is the right home but needs an option.
wrap: existing utility is too low-level, so add a domain wrapper.
new: no suitable utility exists.
Step 9: put the classification in the response.
The final pre-edit note should be tiny.
It should include the checked paths.
It should include the chosen route.
It should include one sentence of reasoning.
Step 10: edit only after that.
This is the line that saves the repo.
The agent must not write the helper first and justify it later.
That pattern is how duplicate utilities sneak in wearing a little fake mustache.
The minimum lookup receipt
Ask the agent for a lookup receipt.
I like this format.
### Reuse lookup
- Capability: CSV export for report rows
- Searched: `csv`, `export`, `escape`, `download`
- Paths checked:
- `packages/shared/export/csv.ts`
- `packages/shared/export/csv.test.ts`
- `src/reports/downloadReport.ts`
- Decision: reuse
- Reason: existing `rowsToCsv` already handles escaping and line endings
- Edit target: wire report rows into `rowsToCsv`, no new utility
This is not paperwork for the sake of paperwork.
It is a compact proof that the agent looked before building.
It also gives reviewers a search trail.
When the decision is wrong, the reviewer can correct the search terms.
That correction can then become memory or a project rule.
That is how the system improves without turning every prompt into a novel.
Before prompt
Here is the kind of prompt that causes duplicate helpers.
Add CSV export to the reports page.
Make sure tests pass.
Follow existing style.
It sounds reasonable.
It is too open.
It tells the agent to implement.
It does not tell the agent to discover.
It treats “existing style” as enough context.
For a coding agent, that often means “copy the nearest pattern.”
The nearest pattern may be the wrong abstraction.
The nearest file may be a feature file.
The correct utility may live three directories away.
The agent will not always pay that cost unless the task makes it mandatory.
After prompt
Here is the replacement prompt I use.
Add CSV export to the reports page.
Before editing, do a reuse lookup:
- Search for existing CSV/export/download helpers.
- Check source, shared packages, tests, docs, CLAUDE.md, and .claude/rules if present.
- Return a short lookup receipt with paths checked.
- Classify the implementation as reuse, extend, wrap, or new.
Only create a new utility if no suitable existing utility exists.
If you create or extend a utility, add or update the closest test.
Keep the final diff scoped to the report export behavior.
This prompt changes the agent’s first move.
The first move is no longer “write code.”
The first move is “prove the repo does not already have the thing.”
That one shift prevents a surprising amount of cleanup.
It also makes the review less emotional.
You are not arguing with the agent’s implementation taste.
You are checking the lookup receipt.
A stricter prompt for shared libraries
Shared libraries deserve a harder gate.
Use this when the repo has a lib, packages/shared, utils, or core directory.
You are working in a repo with shared utilities.
Hard rule:
Do not add a helper under a feature directory until you have searched shared utility locations.
Required lookup:
1. Search for existing function names and domain synonyms.
2. Search tests for the same behavior.
3. Search docs and agent instructions for utility ownership.
4. Return checked paths and a reuse/extend/wrap/new decision.
If the decision is "new", explain why existing candidates are not suitable.
If the decision is "extend", keep backward compatibility unless explicitly told otherwise.
If the decision is "wrap", name the lower-level helper being wrapped.
This is the prompt I would put into a reusable command or skill.
I would not put all of it into every conversation.
The point is to make the lookup behavior reusable without making the global context enormous.
Where CLAUDE.md helps
CLAUDE.md is useful for the rule that should be true every session.
In this case, I would add a short project instruction.
## Reuse before creating utilities
Before adding a new helper, component, API wrapper, parser, formatter, or script:
- search existing source, shared packages, tests, docs, and project rules;
- return a short lookup receipt with paths checked;
- classify the decision as reuse, extend, wrap, or new.
Keep it short.
Anthropic’s Claude Code docs recommend concise, specific instructions.
They also note that longer memory files consume context and can reduce adherence.
That matches field experience.
If CLAUDE.md becomes a warehouse, agents stop treating it like a map.
Put the always-true rule there.
Move the detailed lookup procedure into a command, skill, or path-scoped rule.
Where path-scoped rules help
Path-scoped rules are useful when duplicate utilities happen in one part of the repo.
Example: frontend feature teams keep creating date formatters.
Example: backend services keep creating request retry wrappers.
Example: data pipelines keep creating path normalization helpers.
In those cases, a global rule is too blunt.
Use a path rule instead.
---
paths:
- "src/features/reports/**/*.ts"
- "src/features/reports/**/*.tsx"
---
# Reports utility reuse
Before adding report-specific helpers, check:
- `packages/shared/export/`
- `packages/shared/date/`
- `src/reports/shared/`
- existing report export tests
This keeps context closer to the files where it matters.
It also avoids punishing the whole repo for one team’s recurring mistake.
That is polite engineering.
The repo appreciates manners, even if it cannot say thank you.
Where auto memory helps
Auto memory is useful after a correction.
For example, the reviewer says:
We already use packages/shared/export/csv.ts for all CSV output. Do not add feature-local CSV helpers.
That is exactly the kind of learning worth retaining.
But memory should not be the only line of defense.
Memory can be stale.
Memory can be incomplete.
Memory may be local to one machine or one agent setup.
The source of truth should still live in the repo.
Use memory to improve agent behavior.
Use repo files to preserve team behavior.
Those are different jobs.
Mixing them is how teams end up with “it works on my Claude” as a process.
That sentence should never be allowed near production.
Where MCP helps
MCP can help when the missing context lives outside the checkout.
The official Model Context Protocol docs describe servers exposing tools, resources, and prompts.
That is useful when the agent needs issue data, API docs, database schema, design files, or repository metadata.
Claude Code’s MCP docs also show that resources can be referenced with @ mentions.
That can make external context easier to attach.
GitHub’s official MCP server can expose GitHub capabilities.
Its toolsets can be narrowed so the agent gets only the API groups it needs.
That matters because a giant tool surface is not the same thing as good context.
For duplicate utility prevention, MCP is useful in three cases.
First, the canonical utility list lives in GitHub issues or repo docs not checked out locally.
Second, the repo is too large and remote code search is the normal discovery path.
Third, the team already has a catalog service for packages, APIs, or design tokens.
In those cases, MCP can make lookup cheaper.
But MCP should not be the first answer.
If rg can find the helper in two seconds, you do not need a server.
You need a rule.
When not to add another MCP
Do not add another MCP server because the agent made one duplicate helper.
That is a process failure, not an integration gap.
Do not add another MCP server when the data is already in the local repo.
Use local search first.
Do not add another MCP server when nobody can say which lookup it improves.
More tools can still mean worse decisions.
Do not add another MCP server if it grants broad write access for a read-only lookup problem.
That is buying a forklift to move a sandwich.
Do not add another MCP server when the same result can be captured in CLAUDE.md, a path rule, or a small script.
The boring solution is often the durable one.
Do not add another MCP server just because “agents need context.”
Agents need relevant context.
They need the right context at the right moment.
They need a way to prove what they checked.
That is narrower than “connect everything.”
A small repo utility index
For teams that keep hitting this problem, create a tiny utility index.
Keep it human-readable.
Keep it close to the code.
Do not make it a second documentation platform.
Example:
# Utility index
## Export
- CSV rows: `packages/shared/export/csv.ts`
- Browser downloads: `packages/shared/export/download.ts`
- Report filenames: `src/reports/shared/fileNames.ts`
## Dates
- User-facing date format: `packages/shared/date/formatUserDate.ts`
- ISO parsing: `packages/shared/date/parseIso.ts`
## Money
- Currency input normalization: `packages/shared/money/normalizeInput.ts`
- Display formatting: `packages/shared/money/formatMoney.ts`
Then tell the agent where it lives.
This is not glamorous.
It works because it makes the correct path visible.
The agent does not need perfect semantic search if the repo gives it a small signpost.
The signpost can be incomplete.
It only needs to cover the repeat offenders.
The failure log format
When a duplicate slips through, log it.
Do not write an essay.
Use five fields.
## Duplicate utility failure
- Date: 2026-04-28
- Task: Add CSV export to reports page
- Duplicate created: `src/features/reports/toCsv.ts`
- Existing utility missed: `packages/shared/export/csv.ts`
- Missed search terms: `csv`, `export`, `rowsToCsv`
- Prevention: add `packages/shared/export/` to report lookup rule
This log is boring in the best way.
It turns embarrassment into training data.
It gives you exact search terms.
It tells you whether the fix belongs in docs, rules, tests, or prompts.
It also avoids blaming the model as a vague cloud of failure.
The model did not “not understand the repo.”
The workflow failed to make the repo discoverable.
That is fixable.
What to do after the first failure
After one duplicate, do not build infrastructure.
Patch the prompt.
Add the lookup receipt requirement.
Point to the existing utility.
Maybe add one sentence to CLAUDE.md.
After two duplicates in the same area, add a path-scoped rule.
Name the known utility directory.
Add a test that would fail if the new helper bypasses the shared behavior.
After three duplicates across teams, create a utility index.
Make it owned.
Make it part of review.
After repeated misses where the right context is outside the repo, evaluate MCP.
Start read-only.
Limit toolsets.
Document which lookup it replaces.
That sequence keeps the cure proportional to the disease.
Very important.
Developer tooling loves over-treatment.
The review checklist
Use this checklist on agent-authored diffs.
-
Did the agent add a new helper, parser, formatter, wrapper, hook, component, script, or API client?
-
Did it include a lookup receipt before editing?
-
Are the checked paths plausible?
-
Did it search tests, not just source?
-
Did it search docs or agent instructions when the repo has them?
-
Is the decision labeled reuse, extend, wrap, or new?
-
If new, are existing candidates named and rejected with reasons?
-
If extend, is backward compatibility tested?
-
If wrap, is the wrapped lower-level helper named?
-
If reuse, did the diff avoid adding a second abstraction?
-
Did the final tests exercise the shared behavior?
-
Did the final response mention any utility index or rule that should be updated later?
This checklist should be fast.
If it takes longer than reviewing the code, trim it.
The goal is not a ceremony.
The goal is to catch duplicate helpers while the diff is still small.
A practical example
Imagine the task is:
Add formatted billing amounts to the invoice preview.
The agent wants to add:
function formatCurrency(value: number) {
return `$${value.toFixed(2)}`;
}
That helper is a tiny trap.
It passes the visible example.
It probably fails locale rules.
It may skip cents normalization.
It may disagree with the payment receipt.
The lookup receipt should force a different path.
### Reuse lookup
- Capability: display billing amounts in invoice preview
- Searched: `currency`, `money`, `amount`, `formatCurrency`, `Intl.NumberFormat`
- Paths checked:
- `packages/shared/money/formatMoney.ts`
- `packages/shared/money/formatMoney.test.ts`
- `src/billing/receipt/ReceiptAmount.tsx`
- Decision: reuse
- Reason: `formatMoney` already handles currency code, locale, and cents
- Edit target: call `formatMoney` from invoice preview
Now the code gets simpler.
The behavior stays consistent.
The test can assert the invoice preview uses the same output contract.
No new helper franchise today.
Small victory.
The test angle
Tests should not only prove the feature works.
They should prove the feature uses the shared behavior when that behavior matters.
That does not always mean mocking the utility.
Often the better test is a shared edge case.
For CSV, test commas and quotes.
For dates, test timezone boundaries.
For money, test cents and locale.
For retry, test jitter boundaries and retry count.
For path helpers, test workspace-relative paths.
The point is to make duplicate local helpers expensive.
If a local helper skips the edge case, the test should fail.
That is better than hoping reviewers notice every tiny formatter.
Hope is a bad static analysis tool.
The script option
If the same lookup happens often, wrap it in a script.
Example:
./scripts/find-utility csv export escape
The script can run rg across agreed directories.
It can include tests and docs.
It can exclude build output.
It can print candidate paths.
It can stay dumb.
Dumb scripts are underrated.
They are easy to inspect.
They are easy to change.
They do not require a new permission model.
They also give the agent a concrete command to run.
Concrete commands beat vague instructions.
Run ./scripts/find-utility before adding helpers is better than be careful not to duplicate code.
The first instruction can be verified.
The second instruction is a motivational poster.
The command or skill option
If your environment supports reusable commands or skills, put the lookup flow there.
That keeps the main prompt smaller.
It also gives reviewers a shared vocabulary.
For example:
/reuse-check csv export reports
or:
Use the repo-reuse-check skill before editing.
The command should return the lookup receipt.
It should not edit files.
Keep discovery and editing separate.
That separation is the whole trick.
Once discovery and editing merge, the agent starts defending code it already wrote.
Agents can be very convincing after they have created something.
Developers can be too.
This is not an AI-only flaw.
It is just faster now.
The repo map option
Some teams maintain a repo map.
That can help if the map is short.
A useful repo map says:
-
shared utilities live in
packages/shared; -
feature-local helpers are allowed only when domain-specific;
-
tests for shared utilities live next to the utility;
-
scripts live in
scripts; -
generated files are not source of truth.
An unhelpful repo map lists every folder in the repo.
That is not a map.
That is a phone book.
Agents need landmarks.
They do not need a printed directory tree for the entire city.
The permission angle
Duplicate utilities are not usually a security incident.
But the prevention workflow touches permission design.
If the task only needs local lookup, the agent does not need broad remote access.
If the task needs GitHub issue context, a read-only GitHub MCP setup may be enough.
If the task needs package registry data, use the narrowest integration that answers that question.
GitHub’s MCP server supports limiting toolsets and individual tools.
That is the right instinct.
Give the agent the smallest useful surface.
Small surfaces are easier to reason about.
They also reduce accidental context noise.
The goal is not to starve the agent.
The goal is to stop feeding it the entire pantry when it asked for a spoon.
The local evidence from my own vault
This pattern matches earlier TECHTAEK notes on context discipline.
The recurring theme is not “add more context.”
The recurring theme is “make the right context load at the right moment.”
A prior Claude Code context discipline note treated CLAUDE.md, memory, MCPs, and subagents as separate tools with separate jobs.
That remains the correct frame.
A separate MCP pruning note argued that adding servers should follow an operating need, not tool collecting.
That applies here too.
The local vibe-coding dependency check template says the same thing in plainer workflow terms.
Before building new APIs, components, or utilities, check what already exists.
The repeated Korean note is basically: avoid duplicate code, reuse existing functionality when possible.
That is not a glamorous insight.
It is still where many agent diffs go wrong.
The English version for 2026 is:
Do not trust a passed test until the agent has shown the reuse lookup.
A compact team policy
Here is the policy I would actually ship.
## AI agent utility reuse policy
Before adding a helper, parser, formatter, wrapper, hook, script, or API client, the agent must return a reuse lookup receipt.
The receipt must include:
- capability;
- search terms;
- checked paths;
- decision: reuse, extend, wrap, or new;
- one-sentence reason.
The agent may edit only after the receipt.
If a duplicate utility is found in review, record it in `docs/agent-failures.md` and update the nearest lookup rule.
That is enough for most teams.
It is short enough to remember.
It is concrete enough to enforce.
It is flexible enough not to block real work.
And it gives the agent something measurable to do before touching code.
That is the important part.
The anti-patterns
Anti-pattern 1: “Always reuse existing code.”
Too vague.
The agent can say it tried.
Nobody knows what happened.
Anti-pattern 2: “Read the whole repo first.”
Too expensive.
Also impossible in large repos.
Anti-pattern 3: “Use MCP for repo intelligence.”
Maybe.
But only after you know which intelligence is missing.
Anti-pattern 4: “Put every utility path in the system prompt.”
This bloats context and decays quickly.
Anti-pattern 5: “Let tests decide.”
Tests decide behavior.
Architecture needs ownership.
Anti-pattern 6: “Reviewers will catch it.”
Sometimes.
But tired reviewers miss tiny helpers.
That is how duplicates breed.
Quietly.
In corners.
With sensible names.
The lightweight escalation ladder
Start with prompt language.
Then add a CLAUDE.md rule.
Then add path-scoped rules for repeat zones.
Then add a utility index for repeat categories.
Then add a script or command for common lookup.
Then add read-only MCP if the lookup crosses repo boundaries.
Then consider stricter managed settings or tool allowlists for large teams.
Do not skip straight to the bottom.
Every layer has maintenance cost.
Every layer can become stale.
Every layer can teach the agent the wrong thing if it is too broad.
The best layer is the smallest layer that prevents the next duplicate.
That sentence is the whole article wearing a smaller jacket.
The field note version
If I had to reduce the workflow to one block, it would be this.
Before editing, prove reuse:
1. Name the capability.
2. Search source, shared packages, tests, docs, and agent rules.
3. Return checked paths.
4. Decide reuse, extend, wrap, or new.
5. Only then edit.
That little block should live near your agent workflow.
It can live in CLAUDE.md.
It can live in a command.
It can live in a PR checklist.
It can live in a team playbook.
The exact location matters less than the enforcement.
The agent must do lookup before creation.
The reviewer must expect a receipt.
The failure log must update the next rule.
That loop is how a repo teaches its agents.
FAQ
Is this only a Claude Code problem?
No.
This is an agentic coding problem.
Any tool that can create multi-file diffs can recreate utilities.
Claude Code is useful to discuss because its docs clearly separate memory, settings, rules, and MCP behavior.
But the workflow applies to Codex, Cursor, Gemini CLI, and other coding agents.
Should every small helper require a lookup receipt?
No.
Use judgment.
A one-off inline transformation inside a tiny feature may not deserve ceremony.
But a named helper does.
A shared-looking helper does.
A parser, formatter, retry wrapper, API client, hook, or script definitely does.
The question is whether future code might reasonably reuse it.
If yes, require lookup.
What if lookup takes longer than writing the helper?
That is often the point.
Writing a duplicate helper is cheap today.
Maintaining three versions is expensive later.
Still, lookup should be bounded.
Use three to five search terms.
Inspect the top candidates.
Move on when evidence is enough.
Do not turn lookup into archaeology cosplay.
What if the existing utility is bad?
Then the decision is probably extend or wrap, not silent duplication.
Name the problem.
Add a test around the behavior.
Improve the shared utility if that is safe.
Wrap it if the feature needs a domain-specific contract.
Create a new helper only when the old one is genuinely the wrong home.
Should the failure log be public in the repo?
For team learning, yes.
It does not need blame.
It needs patterns.
The log should show what search term failed, what path was missed, and what rule changed.
That is enough.
Does MCP Tool Search solve context bloat?
It helps with tool definition loading in supported Claude Code setups.
Anthropic’s docs say Tool Search defers MCP tool definitions and loads only names at startup.
That is good.
It does not remove the need to decide whether a server should exist.
Tool discovery efficiency is not the same as workflow discipline.
Related reading
Sources
-
Local reference:
02.Areas/dev/vibecoding/vibe-coding-template/docs/prompts/04-dependency-check.md -
Local reference:
02.Areas/dev/vibecoding/[λΌμΈνλ¬μ€ Tech Talk] λλ μ΄ λ°μ νκ³ ν κ°λ°μλ₯Ό μν AI νμ©λ² - νκΈ° + μ¬λΌμ΄λ PDF.md -
Local reference:
02.Areas/blog/ai_llm/en/260421-claude-code-context-discipline-memory-mcp-subagents.md
Closing note
The practical answer is not “make the agent smarter.”
The practical answer is “make reuse visible before invention starts.”
A repo lookup receipt is small.
A failure log is small.
A path rule is small.
Together, they change the default motion of the agent.
Instead of creating the nearest plausible helper, it has to ask:
What does this repo already know?
That is the question worth making cheap.