Answers · agent safety
How do I make Claude Code reuse my existing components instead of writing new ones?
Short answer: Tell it what exists, give it rules it can check, and enforce the rules outside the prompt. Claude Code reads CLAUDE.md at the start of every session, so put a short component inventory there (name, import path, what each one is for) and concrete rules such as "use Button from @/components/ui/button; never write a raw <button>". Ask it to search the codebase before creating any new component. Claude Code's own docs say CLAUDE.md is context, not enforced configuration, so back the rules with checks that fail: a lint rule that forbids raw elements and duplicate imports, a hook that runs the linter after every edit, and a CI job (lint plus Playwright screenshot tests) that blocks the merge when the agent writes its own version anyway.
Checklist
- Write a component inventory into CLAUDE.md (or a file it imports with @path, such as @docs/components.md): one line per shared component with its import path and when to use it. Keep it short; Anthropic recommends keeping each CLAUDE.md under about 200 lines.
- Make the rules concrete and checkable: "All buttons use <Button> from @/components/ui/button", "Modals use <Dialog>", "Colors come from the theme tokens in tailwind.config.ts, no hex values in components". Vague rules like "follow our design system" are easy to ignore.
- Add a search-first rule: before creating a new component, search src/components for an existing one (by name and by the element it renders) and say which one you're extending; if none fits, explain why before writing a new one.
- Scope rules to the files they apply to with .claude/rules/ and a paths: field (for example src/components/**/*.tsx), so they load when Claude touches those files instead of filling every session.
- Enforce with lint: eslint-plugin-react's react/forbid-elements can forbid raw <button>, <input> or <dialog> in app code, and ESLint's no-restricted-imports can block importing a second UI library or an old component path. Point the error message at the component to use.
- Run the check while Claude works: a Claude Code PostToolUse hook on Edit and Write can run the linter on the changed file and feed the errors back, so the agent fixes them in the same turn.
- Fail the pull request in CI: run lint and Playwright visual comparisons (expect(page).toHaveScreenshot()) on key pages, so a hand-rolled copy that looks different from the shared component shows up as a diff.
- When Claude still writes a duplicate, add the missed case to CLAUDE.md or the lint config, not just a chat correction, so the next session knows.
Why it writes new components
- Each session starts with a fresh context. Unless the inventory is in CLAUDE.md or Claude searches for it, it doesn't know your Button exists, and writing a new one is the shortest path to a working diff.
- Inconsistent naming makes search fail: if the shared component is called PrimaryAction and lives in lib/ui, a search for "button" misses it. The inventory fixes that by mapping purpose to path.
- Conflicting instructions get followed arbitrarily: an old note saying "use MUI" next to a new rule saying "use our components" lets either win. Review and prune the instruction files.
Instructions vs enforcement
- CLAUDE.md, imports and rules shape what Claude tries to do; they don't guarantee it. Anything that must hold every time belongs in a hook, a lint rule or a CI check.
- Hooks run as shell commands at fixed points (PreToolUse, PostToolUse and others) whatever the model decides, so a lint hook after edits catches raw elements before you review.
- CI is the backstop for every author, human or agent: if the rule matters, the merge should fail without it.
Ask Rightshold (free, no key)
Add the MCP server https://www.rightshold.com/mcp to Claude, Cursor, ChatGPT, Grok or VS Code, then ask: “Claude Code added two new UI packages instead of using our components. Are their licenses OK for our commercial app?”
{
"tool": "check_rights_batch",
"arguments": {
"intended_use": {
"commercial_use": true,
"redistribution": true
},
"items": [
{
"ref": "<new-ui-package>",
"license": "MIT"
},
{
"ref": "<new-icon-package>",
"license": "<its SPDX id>"
}
]
}
}- No editor? Run curl -fsSL https://www.rightshold.com/check.mjs | node - in your project folder. It checks the direct dependencies in package.json and sends only license ids, never your code or file names.
- license_compatibility checks whether the licenses can be combined in one product and which duties apply together.
Rightshold does not read your codebase, review pull requests or enforce coding rules; component reuse is enforced by your CLAUDE.md, lint config, hooks and CI. It checks the licenses of packages an agent adds.
Sources
- Claude Code docs: How Claude remembers your project (CLAUDE.md, imports, .claude/rules)
- Claude Code docs: Hooks reference
- eslint-plugin-react: forbid-elements
- ESLint: no-restricted-imports
- Playwright: Visual comparisons
Related
- How to stop an AI coding agent from deleting your production database
- How to check licenses of all npm dependencies before shipping
- How to vet an MCP server before installing it
Not legal advice. Updated 2026-10-29. JSON · Markdown · All answers
Get a free API key in one call
No card and no email needed. 100 free credits; the license and policy checks stay free without a key.
curl -s -X POST https://www.rightshold.com/v1/accounts/signup -H "Content-Type: application/json" -d '{"accept_terms":true}'
export RIGHTSHOLD_API_KEY=rs_... # the api_key from the reply (shown once)
curl -s https://www.rightshold.com/v1/licenses/check -H "Authorization: Bearer $RIGHTSHOLD_API_KEY" \
-H "Content-Type: application/json" -d '{"license":"MIT","intended_use":{"commercial_use":true}}'
