fix(skills): derive paths from nested Claude Code skill roots on import - #2574
Merged
Conversation
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.
Background
Closes #2556.
Claude Code v2.1.178+ loads skills from nested
.claude/skills/directories, and scopes them by location: a skill inapps/web/.claude/skills/deployis only available while working underapps/web.rulesync importdiscovers those nested roots, buttoRulesyncSkill()emitted them identically to a root skill. Since generation always targets the project-root.claude/skills/, a latergeneraterewrote the nested skill to the root with global activation (and left the nested original in place, so Claude Code then saw both under qualified names).Solution
On import, derive the subtree glob from the discovering root and write it as
claudecode.paths.The nested root is already carried on the skill instance:
getConfiguredImportRootsreturns each root'srelativeDirPath(e.g.apps/web/.claude/skills),skills-processorpasses it intofromDir, andloadSkillDirContentstores it on theToolSkill. So no new plumbing was needed —toRulesyncSkill()derives the glob fromthis.relativeDirPathvia the new exportedderiveNestedSkillPaths()helper.Behavior (documented in
docs/reference/file-formats.md):<subtree>/.claude/skillsgetspaths: ["<subtree>/**"].pathsvalue the author already declared in frontmatter is kept verbatim and never overwritten (including the empty-string / empty-array forms already covered by tests)..claude/skills) are unchanged — nopathsis added..claude/skillsdirectory (e.g..claude/scheduled-tasks, nested or not) derives nothing.The flat-namespace clash rule is untouched: on a name clash the root skill still wins the import, so a derived
pathscan never shadow a root skill.Testing
deriveNestedSkillPaths()and for thetoRulesyncSkill()cases above (nested root derives, explicitpathspreserved, root skill unchanged, nested scheduled-tasks root unchanged).npx vitest run --config vitest.e2e.config.ts src/e2e/e2e-skills.spec.ts— 130 passed.pnpm cicheck— all green.🤖 Generated with Claude Code