| name | claude-code-skill-dedup |
| description | Detect and remove duplicate /slash-command registrations in Claude Code caused by the same name existing in multiple source locations (.claude/commands/, .claude/skills/, ~/.claude/skills/). Auto-activates when slash commands appear multiple times in /help or the available skills list, or when investigating duplicate command entries. Trigger keywords: duplicate command, duplicate skill, appears twice, multiple registrations, slash command list, commands skills overlap. |
| license | MIT |
| metadata | {"version":"1.2.2","hermes":{"tags":["Claude-Code","Skills","Deduplication","Troubleshooting"],"related_skills":["skill-registration-troubleshooting","commands-audit"]}} |
Claude Code Skill Dedup
Why Duplicates Occur
Claude Code registers slash commands from three locations simultaneously:
.claude/commands/*.md (project commands)
.claude/skills/*/SKILL.md (project skills)
~/.claude/skills/*/SKILL.md (global skills, symlinked from ~/src/skills/)
The same name in any two sources creates a duplicate entry in the slash command list.
Names compared below are filesystem names; if a skill's frontmatter name: differs from its
folder name, the registration follows the frontmatter — spot-check it before trusting a
match or a miss.
Detection
Run from the project root:
import os
def names(path, strip_md=False):
p = os.path.expanduser(path)
if not os.path.isdir(p):
return set()
return set(f[:-3] if strip_md and f.endswith('.md') else f
for f in os.listdir(p) if not f.startswith('.'))
project_commands = names('.claude/commands/', strip_md=True)
project_skills = names('.claude/skills/')
global_skills = names('~/.claude/skills/')
print("Command + project skill:", sorted(project_commands & project_skills))
print("Command + global skill:", sorted(project_commands & global_skills))
print("Project skill + global skill:", sorted(project_skills & global_skills))
Resolution
Keep the skill, remove the command or global duplicate.
| Duplicate type | Action |
|---|
| Command + skill (project or global) | rm .claude/commands/<name>.md |
| Project skill + global skill | Remove the symlink (see below); source removal is optional and gated |
Removing a global skill symlink
target=$(readlink -f ~/.claude/skills/<name>)
rm ~/.claude/skills/<name>
Delete the source directory only as a separate, deliberate step — and only after
git -C "$target" status shows it clean and pushed (or you know it is disposable scratch).
rm -rf on the source of an unpushed skill destroys the only copy; the dedup never requires it.
Some source dirs are nested (e.g. ~/src/skills/subdir/<name>/) — that is why the resolved
readlink -f path matters before any removal.
If ~/.claude/skills/<name> is a real directory (copied install, not a symlink), readlink
prints nothing — the directory IS the source: apply the same git gate to it before removing.
Verify the fix: re-run the detection script — all three intersection lines must print
empty — before restarting the session.
Restart the Claude Code session after cleanup for the list to refresh [behavior as of 2026-07 — verify on your harness version].