Skip to content

fix(relink): don't clobber foreign skills that share a gstack name#2119

Open
smblight wants to merge 1 commit into
garrytan:mainfrom
smblight:fix/relink-preserve-foreign-skills
Open

fix(relink): don't clobber foreign skills that share a gstack name#2119
smblight wants to merge 1 commit into
garrytan:mainfrom
smblight:fix/relink-preserve-foreign-skills

Conversation

@smblight

Copy link
Copy Markdown

Problem

Installing gstack with --prefix (or any later ./setup run) silently deletes a user's own flat-named skills when they share a name with a gstack skill. Concretely: a personal /qa or /review skill symlinked into ~/.agents/skills/ gets removed.

It's not a one-time hit — setup calls gstack-relink as a self-healing step on every run, so each upgrade re-deletes them.

Root cause

bin/gstack-relink_cleanup_skill_entry() removed any flat-named entry matching a gstack skill name, with no check on where the symlink points:

if [ -L "$entry" ]; then
  rm -f "$entry"            # deletes foreign symlinks too

In prefix mode the deletion isn't even necessary — gstack installs under gstack-* names — so the removed flat entry was pure collateral.

Fix

Scope the cleanup to gstack-owned entries only: remove a flat entry just when its symlink (or its dir's SKILL.md symlink) resolves into the gstack install dir. This mirrors the provenance check setup's cleanup_old_claude_symlinks() already uses, and also handles installs not literally named gstack/ via an $INSTALL_DIR-prefix match.

Result: foreign skills sharing a gstack name are preserved; genuinely stale flat-mode gstack entries are still cleaned.

Tests

Two regression tests added to test/relink.test.ts:

  • prefix mode preserves foreign flat skills sharing a gstack name — covers both the directory-symlink shape and the real-dir-with-foreign-SKILL.md-symlink shape.
  • prefix mode still removes gstack-owned stale flat entries — guards against over-correcting.

Verified the first test fails on main (foreign /qa deleted → ENOENT) and passes with the fix. Full suite: 26 pass, 0 fail.

bun test test/relink.test.ts

gstack-relink's _cleanup_skill_entry deleted ANY flat-named skill entry
matching a gstack skill name when switching prefix modes — including
symlinks that point outside the gstack install. A user with a personal
/qa or /review skill (e.g. symlinked into ~/.agents/skills/) would have
it silently removed on every ./setup, because setup runs gstack-relink as
a self-healing step. In prefix mode the deletion isn't even needed:
gstack installs under gstack-* names, so the flat entry it removed was
pure collateral.

Scope the cleanup to gstack-owned entries only: delete a flat entry just
when its symlink (or its dir's SKILL.md symlink) resolves into the gstack
install dir. Foreign skills sharing a gstack name are now preserved, while
genuinely stale flat-mode gstack entries are still cleaned. This mirrors
the provenance check setup's cleanup_old_claude_symlinks() already uses.

Adds two regression tests to test/relink.test.ts: one asserting a foreign
flat skill (both the dir-symlink and real-dir-with-foreign-SKILL.md
shapes) survives a prefix-mode relink, and a companion asserting a
gstack-owned stale flat entry is still cleaned.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@trunk-io

trunk-io Bot commented Jun 26, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant