fix(skills): stop skill install . cloning into the skills dir itself#35
Merged
ralyodio merged 1 commit intoJul 26, 2026
Conversation
skillName() derives the name from the last path segment, so a `.` or `..` source yields the literal name "." / "..". path.join then collapses it and the claude clone target becomes ~/.claude/skills itself (or ~/.claude). On a fresh machine, where ~/.claude/skills does not exist yet, the clone succeeds: the skills root becomes a checkout of that one skill and the command still reports installed. Where skills already exist, git fails with an unexplained "destination path already exists" and the install is dead. Resolve `.` / `..` sources against the filesystem so the skill is named after the directory it points at, and reject them as an explicit --name.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
moshcode skill install .(run from inside the skill repo you just wrote, the most natural way to install it) targets~/.claude/skillsitself instead of a subdirectory...targets~/.claude.Cause
skillName()takes the last path segment of the source, so.and..come back as the literal names"."and"..".skillInstallActionthen builds the clone target withpath.join(claudeSkillsDir(), name), which collapses them:Reproduced
Ran the real install path (
planSkillInstall+runSkillInstall, real git) against a throwaway HOME.Fresh machine,
~/.claude/skillsnot created yet:The clone succeeds, the skills root becomes a checkout of that one skill, and the command reports success. Every later install lands inside that repo's working tree.
Where skills already exist, the same command fails with no explanation of why:
An explicit
--name .reaches the same place, sincesanitizekeeps dots.Fix
src/skills.mjsonly. A./..source is resolved against the filesystem and named after the directory it points at, soskill install .installs the repo you are standing in under its own name. As an explicit--nameit falls back toskill. Nothing else changes: URLs, plain paths, trailing slashes and.gitstripping all resolve exactly as before.Tests
Two added to
test/skills.test.mjs, one on the derived name and one asserting the clone destination stays a direct child of the skills dir. Verified by stashing onlysrc/skills.mjs: both fail on current main, both pass with the fix.Full suite: 141/141 pass (was 139).