[BUG] `~/.claude/commands/*.md` symlinks not followed for slash-command registration (inconsistent with `~/.claude/skills/`)
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code
Summary
Custom slash commands registered via ~/.claude/commands/<name>.md only surface in the TUI command palette when the file is a real file on disk. Symlinks at the same path, even ones that resolve to a valid frontmatter'd markdown body, are silently ignored.
Expected behavior
A symlink at ~/.claude/commands/<name>.md (resolving to a real markdown file with name: + description: frontmatter) should be picked up the same way it is in ~/.claude/skills/<skill-name>/. The skills docs explicitly call this out:
A<skill-name>entry in the enterprise, personal, or project locations can be a symlink to a directory elsewhere on disk. Claude Code follows the symlink and readsSKILL.mdfrom the target directory.
The same should apply to ~/.claude/commands/*.md.
Actual behavior
| Path | Type | Surface in / autocomplete? |
|---|---|---|
| ~/.claude/skills/foo/SKILL.md | symlink to ~/projects/.../foo/SKILL.md | ✅ |
| ~/.claude/commands/foo.md | symlink to ~/projects/.../commands/foo.md | ❌ |
| ~/.claude/commands/foo.md | regular file (same content) | ✅ |
The asymmetry is what makes this feel like a bug rather than a docs gap: two adjacent, documented extension points behave differently for no obvious reason.
Why this matters
A common setup is to keep all skill/command definitions in a central git repo (~/projects/skills) and symlink them into ~/.claude/{skills,commands}/ for syncing. The skills side works; the commands side does not. Workarounds require a sync script that replaces symlinks with copies — fragile, and breaks the single-source-of-truth pattern.
Steps to reproduce
mkdir -p /tmp/cmdsrc && cat > /tmp/cmdsrc/hello.md <<'EOF'
---
name: hello
description: Test slash command
---
Hello world.
EOF
# Real file works
cp /tmp/cmdsrc/hello.md ~/.claude/commands/hello.md
# (restart claude, type `/hello`) → ✅ found
# Symlink silently broken
rm ~/.claude/commands/hello.md
ln -s /tmp/cmdsrc/hello.md ~/.claude/commands/hello.md
ls -la ~/.claude/commands/hello.md
# (restart claude, type `/hello`) → ❌ not in autocomplete
After symlinking, the file is still resolvable (cat ~/.claude/commands/hello.md works), the frontmatter is parseable, and there are no errors logged — it's just absent from the slash-command list.
Workaround (already deployed)
Replace symlinks with cp -L (resolve target, copy as a regular file). Sync scripts must use cp not ln -s for the ~/.claude/commands/ target. This works but is brittle: any tooling expecting the file to be a symlink (or to share inodes with the source) breaks.
Related issues
- #72631 — IDE slash-command palette doesn't index newly-added symlinked skills until window reload. Different angle on the same root area: symlinks being treated inconsistently by slash-command registration. May share an underlying cause.
- #76932 — Remote Control clients only see built-in slash commands; user-level commands and skills from
~/.claudeare never advertised. Related advertisement surface for commands.
Environment
- Claude Code 2.1.218 (Homebrew cask
claude-code@latest) - Platform: macOS (darwin 27.0.0), arm64
- Subscription auth (Max plan)
- iTerm2 / zsh