Skip to content

fix(readiness-core): don't let an unreadable skills path abort the whole scan - #525

Merged
cobusgreyling merged 1 commit into
cobusgreyling:mainfrom
KhaiTrang1995:fix/readiness-core-skill-scan-unreadable-dir
Aug 17, 2026
Merged

fix(readiness-core): don't let an unreadable skills path abort the whole scan#525
cobusgreyling merged 1 commit into
cobusgreyling:mainfrom
KhaiTrang1995:fix/readiness-core-skill-scan-unreadable-dir

Conversation

@KhaiTrang1995

Copy link
Copy Markdown
Contributor

Problem

scanSkillDirectories() only guarded the readdir() call with a prior
fileExists() check, which just proves the path existed at stat() time
-- it doesn't prove readdir() will succeed on it. A skills path that
exists but isn't actually a directory (renamed, a broken checkout, a
stray file artifact) throws ENOTDIR; an unreadable or permission-
denied directory throws EPERM; a path removed between the two calls
throws ENOENT. All three used to propagate out of this function
uncaught, which aborts the entire audit run in loop-audit and
goal-audit (both call this via findSkills) instead of just skipping
that one broken path and scanning the rest.

Fix

tools/mcp-server/src/resolver.ts's listSkills() already handles the
identical shape of problem correctly, wrapping its readdir() in a
try/catch with the same fileExists()-then-readdir() structure. Applied
the same fix here.

Test plan

Added a regression test where .claude/skills exists as a file instead
of a directory, asserting the scan completes and still finds a valid
skill under a sibling path instead of throwing. Full clean rebuild +
npm test: 6/6 passing.

…ole scan

scanSkillDirectories() only guarded the readdir() call with a prior
fileExists() check, which just proves the path existed at stat() time
-- it doesn't prove readdir() will succeed on it. A skills path that
exists but isn't actually a directory (renamed, a broken checkout, a
stray file artifact) throws ENOTDIR; an unreadable or permission-
denied directory throws EPERM; a path removed between the two calls
throws ENOENT. All three used to propagate out of this function
uncaught, which aborts the entire audit run in loop-audit and
goal-audit (both call this via findSkills) instead of just skipping
that one broken path and scanning the rest.

tools/mcp-server/src/resolver.ts's listSkills() already handles the
identical shape of problem correctly, wrapping its readdir() in a
try/catch with the same fileExists()-then-readdir() structure. Applied
the same fix here.

Test plan: added a regression test where .claude/skills exists as a
file instead of a directory, asserting the scan completes and still
finds a valid skill under a sibling path instead of throwing. Full
clean rebuild + npm test: 6/6 passing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@cobusgreyling cobusgreyling left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scanSkillDirectories should skip unreadable/non-dir skills paths instead of aborting the whole audit. Matches mcp-server listSkills() and the ENOTDIR fixture test is good.

@cobusgreyling
cobusgreyling merged commit 476d418 into cobusgreyling:main Aug 17, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants