From 40b4c98f16cce49e9d2694790d79d96e4983650e Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Sun, 1 Feb 2026 05:59:18 +0000 Subject: [PATCH 1/3] fix: improve symlink handling in skills directory (fixes #5408) Improve error handling for symlink-related edge cases in skills discovery: - Add ELOOP (circular symlinks) and ENOTCONN (network drives) error handling - Handle broken symlinks gracefully in scanSkillsDirectory - Add better error logging for unexpected errors This fixes inconsistent behavior when .kilocode/skills is a symlink, especially for network drives or when symlink targets are temporarily unavailable. Fixes #5408 --- src/services/roo-config/index.ts | 20 ++++++++++++++++---- src/services/skills/SkillsManager.ts | 14 ++++++++++++-- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/services/roo-config/index.ts b/src/services/roo-config/index.ts index 211a6d20d80..0a26cea9652 100644 --- a/src/services/roo-config/index.ts +++ b/src/services/roo-config/index.ts @@ -77,8 +77,14 @@ export async function directoryExists(dirPath: string): Promise { const stat = await fs.stat(dirPath) return stat.isDirectory() } catch (error: any) { - // Only catch expected "not found" errors - if (error.code === "ENOENT" || error.code === "ENOTDIR") { + // Handle errors that can occur with symlinks: ENOENT (not found), ENOTDIR (not a directory), + // ELOOP (too many symlinks), ENOTCONN (network connection issue for network drives) + if ( + error.code === "ENOENT" || + error.code === "ENOTDIR" || + error.code === "ELOOP" || + error.code === "ENOTCONN" + ) { return false } // Re-throw unexpected errors (permission, I/O, etc.) @@ -94,8 +100,14 @@ export async function fileExists(filePath: string): Promise { const stat = await fs.stat(filePath) return stat.isFile() } catch (error: any) { - // Only catch expected "not found" errors - if (error.code === "ENOENT" || error.code === "ENOTDIR") { + // Handle errors that can occur with symlinks: ENOENT (not found), ENOTDIR (not a directory), + // ELOOP (too many symlinks), ENOTCONN (network connection issue for network drives) + if ( + error.code === "ENOENT" || + error.code === "ENOTDIR" || + error.code === "ELOOP" || + error.code === "ENOTCONN" + ) { return false } // Re-throw unexpected errors (permission, I/O, etc.) diff --git a/src/services/skills/SkillsManager.ts b/src/services/skills/SkillsManager.ts index 00df898a0c7..bd3737c6f85 100644 --- a/src/services/skills/SkillsManager.ts +++ b/src/services/skills/SkillsManager.ts @@ -62,6 +62,7 @@ export class SkillsManager { try { // Get the real path (resolves if dirPath is a symlink) + // If the symlink is broken, this will throw ENOENT const realDirPath = await fs.realpath(dirPath) // Read directory entries @@ -77,8 +78,17 @@ export class SkillsManager { // Load skill metadata - the skill name comes from the entry name (symlink name if symlinked) await this.loadSkillMetadata(entryPath, source, mode, entryName) } - } catch { - // Directory doesn't exist or can't be read - this is fine + } catch (error: any) { + // Handle symlink-related errors gracefully: + // - ENOENT: Directory/symlink target doesn't exist + // - ELOOP: Too many symbolic links encountered + // - ENOTCONN: Network drive not connected (for symlinks to network paths) + if (error.code === "ENOENT" || error.code === "ELOOP" || error.code === "ENOTCONN") { + // Silently ignore - this is expected for broken symlinks or unavailable network drives + return + } + // Log other unexpected errors for debugging + console.error(`Error scanning skills directory ${dirPath}:`, error) } } From b67879edaa29976c9312b372ecc62f3f5d076d62 Mon Sep 17 00:00:00 2001 From: Kevin van Dijk Date: Fri, 13 Feb 2026 16:08:42 +0100 Subject: [PATCH 2/3] Add changeset --- .changeset/weak-seas-add.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/weak-seas-add.md diff --git a/.changeset/weak-seas-add.md b/.changeset/weak-seas-add.md new file mode 100644 index 00000000000..1e22afd0c70 --- /dev/null +++ b/.changeset/weak-seas-add.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +fix: improve symlink handling in skills directory From b3c005b85013fe29e51fa5558f7e390af3846f7c Mon Sep 17 00:00:00 2001 From: Kevin van Dijk Date: Fri, 13 Feb 2026 16:17:22 +0100 Subject: [PATCH 3/3] Add markers --- src/services/roo-config/index.ts | 6 ++++-- src/services/skills/SkillsManager.ts | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/services/roo-config/index.ts b/src/services/roo-config/index.ts index 4f6ef18c6e0..dd6f34ca193 100644 --- a/src/services/roo-config/index.ts +++ b/src/services/roo-config/index.ts @@ -86,7 +86,7 @@ export async function directoryExists(dirPath: string): Promise { const stat = await fs.stat(dirPath) return stat.isDirectory() } catch (error: any) { - // Handle errors that can occur with symlinks: ENOENT (not found), ENOTDIR (not a directory), + // kilocode_change start: Handle errors that can occur with symlinks: ENOENT (not found), ENOTDIR (not a directory), // ELOOP (too many symlinks), ENOTCONN (network connection issue for network drives) if ( error.code === "ENOENT" || @@ -96,6 +96,7 @@ export async function directoryExists(dirPath: string): Promise { ) { return false } + // kilocode_change end // Re-throw unexpected errors (permission, I/O, etc.) throw error } @@ -109,7 +110,7 @@ export async function fileExists(filePath: string): Promise { const stat = await fs.stat(filePath) return stat.isFile() } catch (error: any) { - // Handle errors that can occur with symlinks: ENOENT (not found), ENOTDIR (not a directory), + // kilocode_change start: Handle errors that can occur with symlinks: ENOENT (not found), ENOTDIR (not a directory), // ELOOP (too many symlinks), ENOTCONN (network connection issue for network drives) if ( error.code === "ENOENT" || @@ -119,6 +120,7 @@ export async function fileExists(filePath: string): Promise { ) { return false } + // kilocode_change end // Re-throw unexpected errors (permission, I/O, etc.) throw error } diff --git a/src/services/skills/SkillsManager.ts b/src/services/skills/SkillsManager.ts index bd3737c6f85..bee1499bfdf 100644 --- a/src/services/skills/SkillsManager.ts +++ b/src/services/skills/SkillsManager.ts @@ -62,7 +62,7 @@ export class SkillsManager { try { // Get the real path (resolves if dirPath is a symlink) - // If the symlink is broken, this will throw ENOENT + // If the symlink is broken, this will throw ENOENT // kilocode_change const realDirPath = await fs.realpath(dirPath) // Read directory entries @@ -78,6 +78,7 @@ export class SkillsManager { // Load skill metadata - the skill name comes from the entry name (symlink name if symlinked) await this.loadSkillMetadata(entryPath, source, mode, entryName) } + // kilocode_change start: Handle symlink-related errors gracefully } catch (error: any) { // Handle symlink-related errors gracefully: // - ENOENT: Directory/symlink target doesn't exist @@ -90,6 +91,7 @@ export class SkillsManager { // Log other unexpected errors for debugging console.error(`Error scanning skills directory ${dirPath}:`, error) } + // kilocode_change end } /**