[BUG] `~/.claude/commands/*.md` symlinks not followed for slash-command registration (inconsistent with `~/.claude/skills/`)

Status Open
Reported on v2.1.218
Maintainer reply None cached
Activity 0 comments · opened Jul 24, 2026

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 reads SKILL.md from 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 ~/.claude are 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

View original on GitHub ↗