Skip to content

fix(skills): stop skill install . cloning into the skills dir itself#35

Merged
ralyodio merged 1 commit into
moshcoder:mainfrom
clawedassistant26:fix/skill-install-dot-source-escapes-skills-dir
Jul 26, 2026
Merged

fix(skills): stop skill install . cloning into the skills dir itself#35
ralyodio merged 1 commit into
moshcoder:mainfrom
clawedassistant26:fix/skill-install-dot-source-escapes-skills-dir

Conversation

@clawedassistant26

Copy link
Copy Markdown
Contributor

moshcode skill install . (run from inside the skill repo you just wrote, the most natural way to install it) targets ~/.claude/skills itself 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 "..". skillInstallAction then builds the clone target with path.join(claudeSkillsDir(), name), which collapses them:

"."  -> ~/.claude/skills
".." -> ~/.claude

Reproduced

Ran the real install path (planSkillInstall + runSkillInstall, real git) against a throwaway HOME.

Fresh machine, ~/.claude/skills not created yet:

git args: clone --depth 1 . /tmp/home/.claude/skills
RESULT:   [{"key":"claude","status":"installed","code":0}]

~/.claude/skills/.git
~/.claude/skills/SKILL.md

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:

fatal: destination path '/tmp/home/.claude/skills' already exists and is not an empty directory.
. -> [{"key":"claude","status":"failed","code":128}]

An explicit --name . reaches the same place, since sanitize keeps dots.

Fix

src/skills.mjs only. A . / .. source is resolved against the filesystem and named after the directory it points at, so skill install . installs the repo you are standing in under its own name. As an explicit --name it falls back to skill. Nothing else changes: URLs, plain paths, trailing slashes and .git stripping 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 only src/skills.mjs: both fail on current main, both pass with the fix.

Full suite: 141/141 pass (was 139).

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.
@ralyodio
ralyodio merged commit 04c10f8 into moshcoder:main Jul 26, 2026
3 checks passed
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.

2 participants