Repository navigation
gstack-uninstall leaves orphaned ~/.claude/skills/gstack-* directories (prefix-mode install) #896
Description
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
doneThe 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
doneWorkaround
rm -rf ~/.claude/skills/gstack-*
Activity
fix: clean up prefix-mode gstack-* directories on uninstall
mvanhorn commentedon Apr 8, 2026
Implemented in #902 - adds glob-based cleanup for prefix-mode gstack-* directories in the Claude skills section, matching the pattern already used for Codex/Factory/Kiro hosts.
garrytan commentedon Apr 16, 2026
Closing — fixed in the current
bin/gstack-uninstallscript, which now comprehensively removes all host directories (claude, codex, factory, kiro, sidecar) and gstack-* prefixed dirs.