fix: root-level skill update breaks directory structure and fails backfill#56
Merged
Merged
Conversation
…kfill
- Root cause: when skillPath is "SKILL.md" (root-level skill), the old
code in checkForUpdate/checkAllUpdates/updateSkill set folderPath to
"SKILL.md" instead of "". This caused two problems:
1. getTreeHash("SKILL.md") returned blob hash instead of root tree hash,
breaking update detection hash comparison
2. updateSkill copied the SKILL.md file over the skill directory,
turning the directory into a file and breaking symlinks
- Extracted deriveFolderPath(from:) helper in SkillManager to eliminate
the duplicated folderPath derivation logic across three methods
- Fixed findCommitForTreeHash in GitService: empty folderPath caused
"git log --format=%H -- """ to fail with empty pathspec error; now
omits the "--" separator for root-level skills
- Added 10 unit tests in RootLevelSkillUpdateTests covering:
- deriveFolderPath logic for root-level and sub-directory skills
- Regression test ensuring old bug does not return
- getTreeHash behavior difference (root tree vs blob hash)
- Directory structure preservation after update
- findCommitForTreeHash backfill for root-level skills
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.
Problem
When updating a root-level skill (SKILL.md directly in the repo root, e.g.
eze-is/web-access), two bugs occur:Directory becomes a file:
updateSkill()derivesfolderPath = "SKILL.md"instead of"", socopyItemcopies the SKILL.md file over the skill directory, breaking the symlink. Claude Code can no longer find the skill.Update detection fails:
checkForUpdate()andcheckAllUpdates()use the same buggyfolderPathderivation, causinggetTreeHashto computegit rev-parse HEAD:SKILL.md(blob hash) instead ofgit rev-parse HEAD:(root tree hash). Hash comparison produces incorrect results.Backfill crashes:
findCommitForTreeHashwith emptyfolderPathrunsgit log --format=%H -- "", which git rejects as an invalid empty pathspec.Root Cause
The lock file stores
skillPath = "SKILL.md"for root-level skills. The inlinefolderPathderivation logic checkedhasSuffix("/SKILL.md")—"SKILL.md"does not end with"/SKILL.md", so it fell through tofolderPath = lockEntry.skillPath(="SKILL.md"), instead of being treated as an empty path.Fix
deriveFolderPath(from:)static helper inSkillManager— handles"SKILL.md"→""explicitly, eliminating duplicated logic acrosscheckForUpdate(),checkAllUpdates(), andupdateSkill()findCommitForTreeHashinGitService— whenfolderPathis empty, omits the--separator to avoid the empty pathspec errorRootLevelSkillUpdateTests.swiftcovering derivation logic, hash behavior, directory preservation, and backfill regressionManual Verification Required
eze-is/web-access) via SkillDeck, verify it appears in Claude Codevercel-labs/skills) still update correctly — no regressionRegression Checklist
find-skills,systematic-debugging)sourceType: "github")