fix(skills): follow symlinked entries in SkillStore.readEntry (#2104)#2124
Conversation
c75487d to
02467c4
Compare
|
Pushed
Out of scope (kept the PR focused): @codex review |
|
Codex Review: Didn't find any major issues. More of your lovely PRs please. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
esengine
left a comment
There was a problem hiding this comment.
Good catch on the root cause, and the broken-symlink test is the right instinct. But the landscape shifted: #2137 (merged) already shipped the symlinked-directory half of this on main, via isSymlinkDirectory (src/skills.ts:252). So this PR now conflicts and the directory branch duplicates what's already there.
The part that's still genuinely missing on main and worth keeping: symlinked flat <name>.md files. Line 260 still does a bare entry.isFile(), which is false for a symlink, so flat-file symlinks are still dropped.
Please:
- Rebase onto current
main. - Drop the now-redundant directory logic — reuse the existing
isSymlinkDirectoryhelper rather than reintroducing thelet isDir = ...block. - Keep just the flat-
.mdsymlink resolution + the broken-symlink skip. - Trim the tests to the two that aren't already on
main: the flat-.mdsymlink case and the broken-symlink case (the symlinked-directory test already landed with #2137 — drop the duplicate to avoid a name clash).
The flat-.md fix is small but real, so it's worth carrying across the rebase rather than closing.
02467c4 to
47e4deb
Compare
|
Done — rebased onto current
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 47e4deb6cd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| writeFileSync(realFile, "---\ndescription: flat via symlink\n---\nbody\n", "utf8"); | ||
| const scannedRoot = join(home, ".reasonix", "skills"); | ||
| mkdirSync(scannedRoot, { recursive: true }); | ||
| symlinkSync(realFile, join(scannedRoot, "shipit.md"), "file"); |
There was a problem hiding this comment.
Guard flat symlink test on Windows privilege failures
When npm run test:coverage runs on the Windows job (.github/workflows/ci.yml includes windows-latest), this symlinkSync(..., "file") can throw EPERM on hosts without Developer Mode/admin, the same privilege issue already handled by the broken-symlink test below and by the existing at-mentions symlink test. Because this call is unguarded, the skills suite can fail before exercising readEntry; wrap it in a capability guard or skip it on win32.
Useful? React with 👍 / 👎.
47e4deb to
1e706dc
Compare
|
Fixed in @codex review |
|
Note on the CI failures: the 3 failing tests ( Verified: all skills tests still pass on my branch; the 3 failing tests fail identically on the current |
…ne#2104) `readEntry` used `Dirent.isDirectory()/isFile()` to classify entries, but both return false for symlinks even when the target is a directory or file — so a `skills.sh`-style `~/.agents/skills/` layout that symlinks every skill into a central repo silently saw zero entries with no error or warning. Resolve symlinks via `statSync` to get the real target type before branching; a broken symlink is skipped silently instead of aborting the whole scan.
1e706dc to
a32db69
Compare
esengine
left a comment
There was a problem hiding this comment.
Re-reviewed — exactly the trim + rebase I asked for. It now reuses the merged isSymlinkDirectory (#2137) for the directory case and adds only the missing piece: symlinked flat <name>.md files, via a parallel isSymlinkFile helper, with isFile = entry.isFile() || (entry.isSymbolicLink() && !isDir && this.isSymlinkFile(...)). The !isDir guard correctly prevents a dir-symlink from also matching the file branch. No duplication of what's already on main, tests cover it, CLEAN + CI green. This was the genuinely-missing case (main's bare entry.isFile() skips flat-md symlinks). Merging.
…dmin) (#2239) The two symlinked-skill tests added in #2137 — "loads skills from symlinked directories (#2104)" and "skips broken symlinks gracefully" — call symlinkSync with no Windows guard, so `npm run verify` fails with EPERM for contributors on Windows that lack Developer Mode / admin. CI's Windows runner has the privilege so it slipped through. Add `.skipIf(process.platform === "win32")`, matching the #2124 symlink tests already in this file. Co-authored-by: yhh <yhh@yhhdeMac-mini.local>
Problem
Closes #2104.
SkillStore.readEntry()classified each entry withDirent.isDirectory()/isFile(). Both return false for symlinks even when the target is a directory or file — so askills.sh-style~/.agents/skills/layout that symlinks every skill into a central repo (e.g.~/.sra/<skill> → ~/.agents/skills/<skill>) silently saw zero entries, with no error or warning.The reporter's case: 22/22 skills under
~/.agents/skills/were symlinks, all silently ignored.Change
src/skills.tsreadEntrynow resolves symlinks viastatSyncbefore the type branch:So a symlinked directory containing
SKILL.mdand a symlinked flat<name>.mdare both discovered exactly like the real entries. Broken symlinks no longer crash the scan; they're just skipped (parity with how the rest oflist()handles unreadable roots).Out of scope
The issue mentions #1464 (same root cause for the @-mention directory listing). That's a separate code path; happy to tackle it in a follow-up if useful, but keeping this PR focused.
Verification
Three new
tests/skills.test.tscases (usingfs.symlinkSync):SKILL.mdshows up inlist(),<name>.mdshows up,npm run verify(build + lint + typecheck + full suite) passes.