Skip to content

fix(skill-downloader): handle in-repo directory symlinks natively#78

Merged
albertodebortoli merged 2 commits intoLucaTools:mainfrom
peterfriese:fix/issue-77-symlink-downloads
Apr 27, 2026
Merged

fix(skill-downloader): handle in-repo directory symlinks natively#78
albertodebortoli merged 2 commits intoLucaTools:mainfrom
peterfriese:fix/issue-77-symlink-downloads

Conversation

@peterfriese
Copy link
Copy Markdown
Contributor

Fixes #77.

Description

When installing a skill that contains a directory symlink in its repository structure (like twostraws/SwiftUI-Agent-Skill starting from commit ed98890), Luca's native downloader would crash with Failed to download skill at path.

Root Cause & Changes

This issue occurred in two different code paths depending on the authentication context:

  1. The GitHub API Path (GitHubSkillTreeClient.swift)

    • The GitHub API represents symlinks as items with type: "blob" and mode: "120000".
    • Previously, GitHubTreeItem only filtered by type == "blob". Consequently, Luca treated symlinks as regular files. When requested via raw.githubusercontent.com, GitHub returns the symlink target path as a raw string (e.g., ../../references), corrupting the structure.
    • Fix: Added the mode property to the decoding struct and explicitly filtered out mode == "120000".
  2. The Local Clone Path (GitRepositorySkillFetcher.swift)

    • The FileManager.default.enumerator correctly skips traversing into symlinks that point to directories because isDirectoryKey evaluates to false for the symlink itself.
    • However, the symlink itself was yielded as a file path.
    • When downloadSkill attempted to read the content via Data(contentsOf: fileURL), the underlying POSIX layer automatically resolved the symlink to the target directory. Reading a directory as a data buffer throws POSIX Error 21 (Is a directory), crashing the downloader.
    • Fix: Added .isSymbolicLinkKey check in the enumerator loop. If the item is a symlink, we resolve it; if the resolved target is a directory, we skip it.

(Since SKILL.md defines the root of a skill, shared assets referenced via symlink are still caught correctly by the deduplication logic without throwing errors).

Testing

  • Tested locally by building luca and verifying that luca install twostraws/SwiftUI-Agent-Skill natively handles the repository structure correctly and successfully symlinks to the active agents cache without requiring the --use-npx flag.

This commit fixes an issue where the skill downloader fails on symlinked directories (like in twostraws/SwiftUI-Agent-Skill).

1. `GitHubSkillTreeClient` now decodes the `mode` property from the Git Tree API and filters out `120000` (symlink) modes, preventing them from being processed as files and downloaded as raw text strings.
2. `GitRepositorySkillFetcher` now inspects `isSymbolicLinkKey` during enumeration. If a symlink points to a directory, it skips traversing it to prevent crashing on `Data(contentsOf:)` (POSIX Error 21 - Is a directory). Deduplication handles the actual skill scope cleanly.
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 27, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Test that GitHubSkillTreeClient excludes tree items with mode "120000"
and that GitRepositorySkillFetcher skips directory symlinks during enumeration.
@albertodebortoli
Copy link
Copy Markdown
Member

Thank you so much @peterfriese 🙏🙏🙏
Extremely appreciated!

@albertodebortoli albertodebortoli added the bugfix Something isn't working label Apr 27, 2026
@albertodebortoli albertodebortoli added this to the 0.18.0 milestone Apr 27, 2026
@albertodebortoli albertodebortoli merged commit 7119906 into LucaTools:main Apr 27, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Native skill downloader fails to handle in-repo symlinks (Error: Failed to download skill at path)

2 participants