fix(filesystem): resolve symlink/junction targets to directory type in readDirectoryEntries#28532
Conversation
…n readDirectoryEntries When isSymbolicLink() is true (Linux symlinks, Windows junction points like OneDrive Desktop), stat() the target to determine if it points to a directory. Previously these entries were classified as 'symlink' and silently filtered out by downstream code (file/index.ts), making symlinked directories invisible in the project picker, @file picker, and file listing on ALL platforms. This fixes the root cause instead of patching individual filter sites. Closes anomalyco#28526, also resolves anomalyco#10365 and anomalyco#16342.
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Updates directory listing logic to better classify filesystem entries, especially symbolic links that point to directories.
Changes:
- Replaces synchronous
mapwith async mapping +Promise.allto allowstatcalls during classification. - Resolves symlink targets with
NFS.statto classify symlinks-to-directories as"directory".
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return await Promise.all( | ||
| entries.map(async (e): Promise<DirEntry> => { | ||
| let type: DirEntry["type"] | ||
| if (e.isDirectory()) { | ||
| type = "directory" | ||
| } else if (e.isSymbolicLink()) { | ||
| try { | ||
| const target = await NFS.stat(join(dirPath, e.name)) | ||
| type = target.isDirectory() ? "directory" : "symlink" | ||
| } catch { | ||
| type = "symlink" | ||
| } | ||
| } else if (e.isFile()) { | ||
| type = "file" | ||
| } else { | ||
| type = "other" | ||
| } | ||
| return { name: e.name, type } | ||
| }), | ||
| ) |
| return await Promise.all( | ||
| entries.map(async (e): Promise<DirEntry> => { | ||
| let type: DirEntry["type"] | ||
| if (e.isDirectory()) { | ||
| type = "directory" | ||
| } else if (e.isSymbolicLink()) { | ||
| try { | ||
| const target = await NFS.stat(join(dirPath, e.name)) | ||
| type = target.isDirectory() ? "directory" : "symlink" | ||
| } catch { | ||
| type = "symlink" | ||
| } | ||
| } else if (e.isFile()) { | ||
| type = "file" | ||
| } else { | ||
| type = "other" | ||
| } | ||
| return { name: e.name, type } | ||
| }), | ||
| ) |
|
The following comment was made by an LLM, it may be inaccurate: Potential duplicate found:
Why they're related: Both PRs address the same core issue of treating symlinked/junction directories as the |
|
Not a duplicate. #28531 uses This PR uses |
Issue for this PR
Closes #28526
Type of change
What does this PR do?
Symlinked directories (Linux
ln -s) and Windows junction points (e.g., OneDrive Desktop) are invisible in the directory picker, @file picker, and file listing becausereadDirectoryEntriesclassifies them as"symlink"and downstream filters skip non-directory types.This fixes the root cause in
packages/core/src/filesystem.ts:readDirectoryEntries: whenisSymbolicLink()is true, stat() the target and return"directory"if the target is a directory. This fixes ALL consumers at once — no downstream filter changes needed.Also resolves long-standing open issues:
How did you verify your code works?
bun test test/file/— 87 pass, 1 skip, 0 fail. No regressions.Screenshots / recordings
N/A, no UI changes.
Checklist