Skills hardcode `~/.claude/skills/gstack/` paths — any install dir not named `gstack` silently breaks after `setup`
- Dominant language
- TypeScript
- Stars
- 133k
- Forks
- 19.9k
- Avg merge
- 18h 46m
- Merged PRs (30d)
- 26
Description
## Summary
Every skill's `SKILL.md` hardcodes the absolute string `~/.claude/skills/gstack/...` for its own `bin/` and asset calls (e.g. the telemetry/config preamble that runs on *every* skill invocation). `setup`, however, is parameterised on its own checkout location and wires the skill symlinks correctly for **any** directory name. The result: if gstack is installed at `~/.claude/skills/` where `` isn't literally `gstack`, the symlinks resolve but the skills' internal `bin`/asset references point at a non-existent `~/.claude/skills/gstack/` and fail **silently, at skill-invocation time**.
This is the `gstack`-dir-name sibling of #349 (`$CLAUDE_CONFIG_DIR`, the `~/.claude` prefix). Same underlying cause — hardcoded absolute self-paths — different segment. Both ladder up to the "make path resolution dynamic rather than hardcoded" direction from #289.
## Root cause (with file:line)
- `setup:17-18` — the install dir is **derived**, not fixed:
```sh
INSTALL_GSTACK_DIR="$(cd "$(dirname "$0")" && pwd)"
SOURCE_GSTACK_DIR="$(cd "$(dirname "$0")" && pwd -P)"
```
So `setup` is designed to run from wherever the repo lives, and `INSTALL_SKILLS_DIR="$(dirname "$INSTALL_GSTACK_DIR")"` (`setup:19`). The per-skill install (`setup:535-569`) creates a real dir at `~/.claude/skills//` with an **absolute** `SKILL.md` symlink into the actual checkout — correct for any dir name.
- But the skill **bodies** are committed with the literal absolute path. Example — `qa/SKILL.md` at HEAD contains 52 occurrences of `~/.claude/skills/gstack/` across 51 lines (the preamble alone calls `~/.claude/skills/gstack/bin/gstack-config`, `.../bin/gstack-telemetry-log`, etc.).
- `setup` **already rewrites** these paths when it generates the non-Claude host copies — e.g. `setup:~1012-1028` rewrites `.claude/skills/gstack` → `.kiro/skills/gstack` (and the codex/cursor/factory/gbrain equivalents). The primary Claude install is the one host that never gets its in-file paths reconciled with `INSTALL_GSTACK_DIR`'s actual basename.
## Reproduction
```sh
# install under any name that isn't "gstack"
git clone https://github.com/garrytan/gstack ~/.claude/skills/gstack-dev
cd ~/.claude/skills/gstack-dev && ./setup
# the skill symlinks are correct, but the in-file paths are not:
grep -c '~/.claude/skills/gstack/' ~/.claude/skills/gstack-dev/qa/SKILL.md # > 0
ls ~/.claude/skills/gstack/bin/gstack-config 2>&1 # No such file or directory
```
Then invoke any skill (e.g. `/qa`): its preamble shells out to `~/.claude/skills/gstack/bin/gstack-config …`, which doesn't exist. No error surfaces to the user at install time — it only fails when a skill runs.
Observed directly while upgrading an install located at `~/.claude/skills/i-gstack`: after `git reset --hard origin/main && ./setup`, every top-level `*/SKILL.md` that references the install path (52 files on that checkout) still pointed at `~/.claude/skills/gstack/`, and `setup` did not reconcile them.
## Impact
- Affects anyone whose global install lives at `~/.claude/skills/` where ` != gstack` (`setup` installs the skill siblings into `INSTALL_SKILLS_DIR="$(dirname "$INSTALL_GSTACK_DIR")"`, so it accepts this layout — it just doesn't reconcile the in-file paths).
- **Silent**: nothing fails at install; skills break later, at invocation, in a way that looks like the skill itself is broken.
- Affects every skill (the shared preamble), not a subset.
## Suggested fixes (maintainer's call)
1. **Compatibility symlink (smallest).** When `basename "$INSTALL_GSTACK_DIR" != gstack`, create/refresh `$INSTALL_SKILLS_DIR/gstack -> $INSTALL_GSTACK_DIR` so the hardcoded references resolve with zero file edits. *Caveat:* must not let Claude Code discover the repo-shaped dir as duplicate skills — `setup:589` already has handling for skipping the repo-shaped `gstack` dir, so this needs to compose with that.
2. **Runtime-relative resolution (cleanest, aligns with #289).** Have the preamble resolve its own bin/asset dir relative to the `SKILL.md` location instead of a hardcoded absolute path (a resolved `$GSTACK_DIR`), removing the dir-name dependency entirely. Larger change (touches the shared preamble across all skills).
3. **Extend the existing rewrite to the Claude host (symmetric).** Apply the same path rewrite `setup` already does for kiro/codex to the primary install, mapping `~/.claude/skills/gstack/` → `$INSTALL_GSTACK_DIR`. *Caveat:* the Claude install symlinks to source rather than copying, so this would either dirty the working tree or require switching that host to a copy-and-rewrite step.
## Workaround (for affected users today)
After each install/upgrade, re-point the hardcoded references in the top-level skill files to the real install dir:
```sh
cd ~/.claude/skills/
# macOS/BSD sed shown; on GNU/Linux use `sed -i` (no '').
find . -maxdepth 2 -name SKILL.md -not -path './.*' -print0 \
| xargs -0 sed -i '' "s|skills/gstack|skills/$(basename "$PWD")|g"
```
Scope to top-level `*/SKILL.md` only; the generated host copies under `.agents/.cursor/.factory/.gbrain/hosts` use a `gstack-` prefix convention and should be left as-is. (This needs re-running after every upgrade, since `git reset --hard` reverts it — which is the motivation for fix #1 or #2.)
## Related
- #349 — `$CLAUDE_CONFIG_DIR` support: the `~/.claude` prefix counterpart of this same hardcoded-self-path problem.
- #289 (closed RFC) — host integration contract / dynamic path resolution: fix #2 above is the concrete form of that direction for the dir-name axis.
Contributor guide
Assessment
This issue has not been assessed yet.