Summary
gstack-uninstall removes ~/.claude/skills/gstack/ but leaves all the per-skill ~/.claude/skills/gstack-* directories behind as empty shells containing dangling SKILL.md symlinks. This happens when gstack is installed in "prefix" mode (where each skill lives in its own directory rather than as a top-level symlink).
Environment
- macOS 15.4 (Darwin 25.4.0), Apple Silicon
- gstack installed via prefix-mode setup at
~/.claude/skills/gstack
- 36 per-skill directories:
gstack-browse, gstack-ship, gstack-plan-ceo-review, etc.
Repro
- Install gstack in prefix mode. The installer creates:
~/.claude/skills/gstack/ (real directory, the toolkit)
~/.claude/skills/gstack-browse/ (real directory, NOT a symlink)
~/.claude/skills/gstack-browse/SKILL.md (symlink → ../gstack/browse/SKILL.md)
~/.claude/skills/gstack-ship/ (real directory, NOT a symlink)
~/.claude/skills/gstack-ship/SKILL.md (symlink → ../gstack/ship/SKILL.md)
... 36 total
- Run
~/.claude/skills/gstack/bin/gstack-uninstall --force
- Output:
Removed: ~/.claude/skills/gstack /Users/<me>/.gstack
gstack uninstalled.
- Inspect:
$ ls ~/.claude/skills/ | grep gstack
gstack-autoplan
gstack-benchmark
gstack-browse
... (35 leftover directories)
$ ls -la ~/.claude/skills/gstack-browse
SKILL.md -> /Users/<me>/.claude/skills/gstack/browse/SKILL.md (target gone)
Expected
All ~/.claude/skills/gstack-* directories created by the installer should be removed, matching the script header docs:
~/.claude/skills/{skill} — per-skill symlinks created by setup
Actual
35 orphaned directories remain. Each contains a single dangling SKILL.md symlink. They show up in Claude Code's skill list at session start until manually rm -rf'd.
Root cause
bin/gstack-uninstall lines 135-143:
for _LINK in "$CLAUDE_SKILLS"/*; do
[ -L "$_LINK" ] || continue # ← bails out for prefix-mode dirs
_NAME="$(basename "$_LINK")"
[ "$_NAME" = "gstack" ] && continue
_TARGET="$(readlink "$_LINK" 2>/dev/null || true)"
case "$_TARGET" in
gstack/*|*/gstack/*) rm -f "$_LINK"; REMOVED+=("claude/$_NAME") ;;
esac
done
The loop only handles entries where ~/.claude/skills/<entry> is itself a symlink. In prefix-mode, each gstack-<name> is a regular directory whose inner SKILL.md is the symlink, so [ -L "$_LINK" ] || continue skips them on the outer pass and the inner symlink is never inspected.
Suggested fix
After the existing loop, add a second pass that handles prefix-mode directories — match gstack-* directories whose SKILL.md is a symlink pointing into gstack/:
for _DIR in "$CLAUDE_SKILLS"/gstack-*; do
[ -d "$_DIR" ] || continue
[ -L "$_DIR/SKILL.md" ] || continue
_TARGET="$(readlink "$_DIR/SKILL.md" 2>/dev/null || true)"
case "$_TARGET" in
*gstack/*) rm -rf "$_DIR"; REMOVED+=("claude/$(basename "$_DIR")") ;;
esac
done
Workaround
rm -rf ~/.claude/skills/gstack-*
Summary
gstack-uninstallremoves~/.claude/skills/gstack/but leaves all the per-skill~/.claude/skills/gstack-*directories behind as empty shells containing danglingSKILL.mdsymlinks. This happens when gstack is installed in "prefix" mode (where each skill lives in its own directory rather than as a top-level symlink).Environment
~/.claude/skills/gstackgstack-browse,gstack-ship,gstack-plan-ceo-review, etc.Repro
~/.claude/skills/gstack/bin/gstack-uninstall --forceExpected
All
~/.claude/skills/gstack-*directories created by the installer should be removed, matching the script header docs:Actual
35 orphaned directories remain. Each contains a single dangling
SKILL.mdsymlink. They show up in Claude Code's skill list at session start until manuallyrm -rf'd.Root cause
bin/gstack-uninstalllines 135-143:The loop only handles entries where
~/.claude/skills/<entry>is itself a symlink. In prefix-mode, eachgstack-<name>is a regular directory whose innerSKILL.mdis the symlink, so[ -L "$_LINK" ] || continueskips them on the outer pass and the inner symlink is never inspected.Suggested fix
After the existing loop, add a second pass that handles prefix-mode directories — match
gstack-*directories whoseSKILL.mdis a symlink pointing intogstack/:Workaround