Summary
On Windows (MSYS2/Git Bash, MINGW64), link_claude_skill_dirs in setup uses ln -snf which silently creates file copies, not symlinks. As a result, every git pull of the gstack repo updates ~/.claude/skills/gstack/<name>/SKILL.md (the source) but leaves the top-level ~/.claude/skills/<name>/SKILL.md (the supposed-symlink) frozen at install-time content. New skills released in upstream get registered (because mkdir -p + new "copy" creation), but updates to existing skills go invisible.
This is silent — setup exits 0, no warning. Users only notice when v1.33.1's task-shaped learnings refresh fails to fire in /investigate / /ship / /qa, or when a skill references a feature that's documented but not present in their installed SKILL.md.
Steps to Reproduce
Minimal repro — no gstack needed, isolates the ln -snf behavior:
D=/tmp/ln-test
mkdir -p "$D"
echo "v1-original" > "$D/src.txt"
ln -snf "$D/src.txt" "$D/link.txt"
file "$D/link.txt"
# Output on Git Bash: /tmp/ln-test/link.txt: ASCII text (NOT "symbolic link to ...")
echo "v2-UPDATED" > "$D/src.txt"
cat "$D/link.txt"
# Output: v1-original <-- proves it's a copy, not a symlink
Full gstack repro:
cd ~/.claude/skills/gstack && git pull
./setup --host claude --quiet
ls -la ~/.claude/skills/investigate/SKILL.md
# First char is `-` (regular file), not `l` (symlink),
# despite setup's comment at line 368 promising "SKILL.md symlink inside".
Expected Behavior
Either:
- Option A: Detect Windows + Developer-Mode-off, fall back to
cp -f. State this clearly in setup output ("note: Windows install uses file copies; re-run setup after every git pull").
- Option B: Set
MSYS=winsymlinks:nativestrict for the link step and document that Developer Mode (or admin) is required on Windows.
- Option C: Use a content-hash check so
setup overwrites stale copies even when re-run. Combined with a .last-link-sha marker so users can detect drift.
Same pattern was applied at commit 999987ef ("fix(ci): use hardlink copy instead of symlink for node_modules cache") — precedent for Option A.
Actual Behavior
ln -snf on Git Bash without Developer Mode creates a plain file copy. setup prints linked skills: ... despite no symlinks existing. No exit-code signal, no warning.
Root Cause
setup line 404:
ln -snf "$gstack_dir/$dir_name/SKILL.md" "$target/SKILL.md"
No IS_WINDOWS branch around the link call. The script DOES detect Windows (line 28-30) and uses IS_WINDOWS in 3 other places (lines 208, 332, 354 — Playwright/Node fallbacks) but not here.
The bin/gstack-relink binary likely has the same issue if it uses ln -s — out of scope for this issue but worth a separate check.
Suggested Patch
Replace line 404:
- ln -snf "$gstack_dir/$dir_name/SKILL.md" "$target/SKILL.md"
+ if [ "$IS_WINDOWS" -eq 1 ]; then
+ cp -f "$gstack_dir/$dir_name/SKILL.md" "$target/SKILL.md"
+ else
+ ln -snf "$gstack_dir/$dir_name/SKILL.md" "$target/SKILL.md"
+ fi
And at end of link_claude_skill_dirs, add a Windows-only note:
if [ "$IS_WINDOWS" -eq 1 ] && [ ${#linked[@]} -gt 0 ]; then
echo " note: Windows install uses file copies. Re-run ./setup after every 'git pull' to refresh skill files."
fi
Workaround (for users on current setup)
After every git pull of gstack:
cd ~/.claude/skills/gstack && for d in */; do
d="${d%/}"
[ "$d" = "node_modules" ] && continue
[ ! -f "$d/SKILL.md" ] && continue
n=$(grep -m1 '^name:' "$d/SKILL.md" 2>/dev/null | sed 's/^name:[[:space:]]*//' | tr -d '[:space:]')
[ -z "$n" ] && n="$d"
mkdir -p "$HOME/.claude/skills/$n"
cp -f "$HOME/.claude/skills/gstack/$d/SKILL.md" "$HOME/.claude/skills/$n/SKILL.md"
done
Environment
- Windows 11 Home 10.0.26200
- Git Bash (MINGW64), bash 5.x
- bun 1.3.13
- gstack v1.33.2.0 (HEAD
dc6252d1)
MSYS env var: unset (default)
- Developer Mode: off
Summary
On Windows (MSYS2/Git Bash, MINGW64),
link_claude_skill_dirsinsetupusesln -snfwhich silently creates file copies, not symlinks. As a result, everygit pullof the gstack repo updates~/.claude/skills/gstack/<name>/SKILL.md(the source) but leaves the top-level~/.claude/skills/<name>/SKILL.md(the supposed-symlink) frozen at install-time content. New skills released in upstream get registered (becausemkdir -p+ new "copy" creation), but updates to existing skills go invisible.This is silent —
setupexits 0, no warning. Users only notice when v1.33.1's task-shaped learnings refresh fails to fire in/investigate//ship//qa, or when a skill references a feature that's documented but not present in their installed SKILL.md.Steps to Reproduce
Minimal repro — no gstack needed, isolates the
ln -snfbehavior:Full gstack repro:
Expected Behavior
Either:
cp -f. State this clearly in setup output ("note: Windows install uses file copies; re-run setup after everygit pull").MSYS=winsymlinks:nativestrictfor the link step and document that Developer Mode (or admin) is required on Windows.setupoverwrites stale copies even when re-run. Combined with a.last-link-shamarker so users can detect drift.Same pattern was applied at commit
999987ef("fix(ci): use hardlink copy instead of symlink for node_modules cache") — precedent for Option A.Actual Behavior
ln -snfon Git Bash without Developer Mode creates a plain file copy.setupprintslinked skills: ...despite no symlinks existing. No exit-code signal, no warning.Root Cause
setupline 404:No
IS_WINDOWSbranch around the link call. The script DOES detect Windows (line 28-30) and usesIS_WINDOWSin 3 other places (lines 208, 332, 354 — Playwright/Node fallbacks) but not here.The
bin/gstack-relinkbinary likely has the same issue if it usesln -s— out of scope for this issue but worth a separate check.Suggested Patch
Replace line 404:
And at end of
link_claude_skill_dirs, add a Windows-only note:Workaround (for users on current setup)
After every
git pullof gstack:Environment
dc6252d1)MSYSenv var: unset (default)