From 564c8ae8bfe64ea7e584c6d3d0c82146c94dc52a Mon Sep 17 00:00:00 2001 From: Junho Yeo Date: Sat, 13 Dec 2025 13:58:02 +0900 Subject: [PATCH] fix: use `lstatSync` instead of `statSync` for symlink detection (#32) --- src/features/claude-code-skill-loader/loader.ts | 10 +++++++--- src/tools/skill/tools.ts | 6 +++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/features/claude-code-skill-loader/loader.ts b/src/features/claude-code-skill-loader/loader.ts index 700c677..6de225f 100644 --- a/src/features/claude-code-skill-loader/loader.ts +++ b/src/features/claude-code-skill-loader/loader.ts @@ -1,4 +1,4 @@ -import { existsSync, readdirSync, readFileSync, statSync, readlinkSync } from "fs" +import { existsSync, readdirSync, readFileSync, lstatSync, readlinkSync } from "fs" import { homedir } from "os" import { join, resolve } from "path" import { parseFrontmatter } from "../../shared/frontmatter" @@ -22,8 +22,12 @@ function loadSkillsFromDir(skillsDir: string, scope: SkillScope): LoadedSkillAsC if (!entry.isDirectory() && !entry.isSymbolicLink()) continue let resolvedPath = skillPath - if (statSync(skillPath, { throwIfNoEntry: false })?.isSymbolicLink()) { - resolvedPath = resolve(skillPath, "..", readlinkSync(skillPath)) + try { + if (lstatSync(skillPath, { throwIfNoEntry: false })?.isSymbolicLink()) { + resolvedPath = resolve(skillPath, "..", readlinkSync(skillPath)) + } + } catch { + continue } const skillMdPath = join(resolvedPath, "SKILL.md") diff --git a/src/tools/skill/tools.ts b/src/tools/skill/tools.ts index 3a1ca94..bcef908 100644 --- a/src/tools/skill/tools.ts +++ b/src/tools/skill/tools.ts @@ -1,5 +1,5 @@ import { tool } from "@opencode-ai/plugin" -import { existsSync, readdirSync, statSync, readlinkSync, readFileSync } from "fs" +import { existsSync, readdirSync, lstatSync, readlinkSync, readFileSync } from "fs" import { homedir } from "os" import { join, resolve, basename } from "path" import { z } from "zod/v4" @@ -39,7 +39,7 @@ function discoverSkillsFromDir( if (entry.isDirectory() || entry.isSymbolicLink()) { let resolvedPath = skillPath try { - const stats = statSync(skillPath, { throwIfNoEntry: false }) + const stats = lstatSync(skillPath, { throwIfNoEntry: false }) if (stats?.isSymbolicLink()) { resolvedPath = resolve(skillPath, "..", readlinkSync(skillPath)) } @@ -85,7 +85,7 @@ const skillListForDescription = availableSkills function resolveSymlink(skillPath: string): string { try { - const stats = statSync(skillPath, { throwIfNoEntry: false }) + const stats = lstatSync(skillPath, { throwIfNoEntry: false }) if (stats?.isSymbolicLink()) { return resolve(skillPath, "..", readlinkSync(skillPath)) }