#2645

fix(install): keep skills-dir plugin folders intact (#2609)

OpenEthereal49wants to merge intofrom

Summary

npx skills add treated a Claude Code skills-dir plugin — a folder with .claude-plugin/plugin.json, hooks/ and skills/<name>/SKILL.md but no root SKILL.md — as an ordinary skill container. It installed only the nested SKILL.md folder, so the installed skill lost its plugin manifest, hooks and sibling skills, and Claude Code could not load it as <name>@skills-dir.

This change installs the whole plugin folder for Claude Code:

  • .claude/skills/<plugin-name>/ (or ~/.claude/skills/… with -g) now contains .claude-plugin/, hooks/ and skills/… in copy, symlink and link modes.
  • Other agents keep installing the nested skill folder, so a plugin skill stays usable where skills-dir plugins do not exist.
  • Plain skill folders and root-SKILL.md claude plugin init scaffolds keep their existing behavior.
  • The GitHub blob fast path falls back to a clone when a discovered skill belongs to a skills-dir plugin, because per-skill snapshots cannot carry the plugin files.

Fixes #2609.

Implementation

  • src/plugin-manifest.ts: findSkillsDirPlugin() walks up from a discovered skill to the nearest folder with .claude-plugin/plugin.json that has no SKILL.md of its own, bounded by the source root.
  • src/skills.ts: records pluginRoot and the manifest pluginName during discovery, including when discovery starts from a skill subpath.
  • src/installer.ts: getSkillInstallUnit() chooses the plugin folder and plugin name for Claude Code; copy, symlink and link use that unit.
  • src/add.ts: installs a plugin once per target when several of its skills are selected, while still reporting each selected skill.
  • src/blob.ts: uses the clone path for trees that contain skills-dir plugins.

Verification

New tests in tests/skills-dir-plugin.test.ts and tests/blob-skills-dir-plugin.test.ts cover discovery (including subpaths), copy mode, symlink mode, other-agent behavior, the root-SKILL.md scaffold and the blob fallback.

Before the fix, the copy-mode test failed with:

FAIL tests/skills-dir-plugin.test.ts > copies the whole plugin folder for Claude Code in copy mode
Error: ENOENT: no such file or directory, open '.../.claude/skills/my-plugin/.claude-plugin/plugin.json'

After the fix:

  • New tests: 7 passed.
  • Targeted install/discovery suites: 107 passed, 0 failed.
  • pnpm vitest run: 1050 passed, 9 failed. All 9 failures reproduce on unmodified 92a9a34 in this environment (macOS /var vs /private/var path assertions, no TTY, and two update tests); none touch this change.
  • pnpm type-check, pnpm format:check, pnpm build: pass.

Not run

  • Loading the installed folder in a real Claude Code 2.1.x session as <name>@skills-dir: not run. The on-disk layout is verified; plugin loading, hooks execution and ${CLAUDE_PLUGIN_ROOT} substitution were not.
  • Agents other than Claude Code and Codex, and Windows symlink behavior: not run.
  • skills list and skills remove still address the nested skill name; this PR changes installation only.

AI disclosure: implemented with the Codex CLI (AI agent) under human review.