Skip to content

fix(projects): treat symlinked directories as directories in Add Project browsers - #8987

Closed
choihjin wants to merge 1 commit into
stablyai:mainfrom
choihjin:fix/add-project-browser-symlink-dirs
Closed

choihjin wants to merge 1 commit into
stablyai:mainfrom
choihjin:fix/add-project-browser-symlink-dirs

Conversation

@choihjin

@choihjin choihjin commented Jul 16, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes #8983.

In the Add Project / Add Remote Project folder browsers, a symbolic link pointing to a directory was listed as a file — no folder icon, not navigable — so any project living behind a symlinked path could not be reached or selected.

Two classification sites had the same bug:

  • browseServerDir (src/main/runtime/orca-runtime.ts) used Dirent.isDirectory(), which does not follow symlinks. Symlink entries are now classified by their target type via stat, mirroring the existing (and test-locked) behavior of the relay fs.readDir handler in src/relay/fs-handler.ts.
  • ssh:browseDir POSIX branch (src/main/ipc/ssh-browse.ts) derived the directory marker from ls -p, which does not dereference links. The listing now appends the / marker via [ -d ] (which follows symlinks) in a plain POSIX sh loop. The ls output is captured into a variable first so a failing ls (e.g. permission denied) still propagates through the && chain as a non-zero exit code instead of being masked by the while-loop pipeline — preserving the existing error semantics.

Deliberately unchanged: the local file-explorer fs:readDir and runtime file listing intentionally treat symlinks as file-like to avoid macOS TCC prompts (documented and test-locked); the Windows remote fallback already classifies symlinked directories correctly via PSIsContainer.

Screenshots

Remote filesystem browser (Add Remote Project) on a real Linux SSH host, running this branch. linked-dir is a symlink to real-dir; it now renders as a navigable directory, sorts with directories, and can be descended into and selected as a project (breadcrumb reached /tmp/orca-symlink-verify/linked-dir):

Remote file browser showing a symlinked directory classified as a folder

Before this change the same entry rendered with a file icon and could not be entered.

Testing

  • pnpm lint
  • pnpm typecheck
  • pnpm test
  • pnpm build
  • Added or updated high-quality tests that would catch regressions, or explained why tests were not needed

New regression tests (both watched failing before the fix):

  • orca-runtime.test.ts: browseServerDir over a real temp directory containing a symlinked directory, a symlinked file, and a broken symlink — asserts symlink-to-dir classifies as a directory, symlink-to-file and broken links stay file-like, and sort order holds. Uses junction on Windows, matching the existing relay symlink test.
  • ssh-browse.test.ts: locks the new POSIX listing command and asserts symlinked-directory entries parse as directories; existing command-string assertions updated via a shared helper.

Manual verification: drove the built dev app end-to-end against a real Linux SSH host — browsed into the symlinked directory and confirmed it is selectable as a project. The new shell snippet was additionally exercised directly in sh against fixtures with spaces in names, an empty directory, a broken symlink, and a permission-denied directory (still exits non-zero).

AI Review Report

Review by Claude (Fable 5), which also authored the change. Checks performed:

  • Cross-platform (macOS/Linux/Windows): the browseServerDir change is pure node:fs and platform-neutral; Node reports Windows junctions as symlinks, and the new test uses junction on win32. The SSH change touches only the POSIX branch; the Windows PowerShell fallback (PSIsContainer, already symlink-correct) is untouched. No shortcuts, labels, or path-separator handling involved.
  • SSH/remote/local: the remote listing keeps a single exec round-trip (no per-entry SSH calls) and preserves exit-code error semantics for permission-denied/unreadable directories (verified in a real shell). Verified live over SSH against a Linux host. The relay/daemon fs.readDir path already handled symlinks and is unchanged.
  • Shell portability: the new snippet uses only POSIX sh constructs (command ls -1A, [ -d ], printf, while IFS= read -r) — no bashisms, no find -maxdepth, no pipefail — so dash/busybox login shells are fine. Filenames with spaces are preserved; filenames containing newlines remain a pre-existing limitation of the line-based protocol, unchanged by this PR.
  • Performance: extra stat calls are issued only for symlink entries and run concurrently; non-symlink entries take the previous fast path. Remote listing adds one subshell and a builtin test per entry inside the existing single exec.
  • Behavioral risk: broken symlinks classify as files (matching relay behavior); the browse target itself was already stat-validated, so selecting a symlinked folder keeps working as before.

Security Audit

  • The remote command keeps the existing shellEscape for the only user-controlled input (the directory path); the added shell text is a static string with no interpolation, and "$f" values come from ls output inside the already-escaped target directory — no new injection surface.
  • No new IPC endpoints, no auth/secrets/dependency changes. Local symlink following is confined to classifying entries of a directory the user explicitly browsed; the relay ACL model is untouched (the raw-exec browse path exists precisely because browsing happens before workspace roots are registered, unchanged).

Notes

  • POSIX remote hosts get the fix via the new listing command; Windows remote hosts were already correct.
  • The local file explorer's intentional "symlinks are file-like" TCC-avoidance behavior is out of scope and unchanged.

ELI5

Symlinked folders in Add Project looked like plain files and couldn’t be opened. They’re treated as real directories now so you can navigate into them and pick projects.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2026b9af-7123-44f2-996b-ac1ed31c6878

📥 Commits

Reviewing files that changed from the base of the PR and between 58a5c33a9e09624584c25f11834d17fabae1348d and 81d99a3.

📒 Files selected for processing (4)
  • src/main/ipc/ssh-browse.test.ts
  • src/main/ipc/ssh-browse.ts
  • src/main/runtime/orca-runtime.test.ts
  • src/main/runtime/orca-runtime.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/main/runtime/orca-runtime.ts
  • src/main/runtime/orca-runtime.test.ts
  • src/main/ipc/ssh-browse.ts
  • src/main/ipc/ssh-browse.test.ts

📝 Walkthrough

Walkthrough

Remote POSIX browsing now captures ls output and appends directory markers using per-entry directory checks, including symlinked directories, while preserving command failure status. Tests centralize expected command construction and cover symlinked directory listings. Local server browsing now resolves symlink targets asynchronously to set isDirectory, retains isSymlink, and tests real, symlinked, and broken entries.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: fixing symlinked directories in Add Project browsers.
Description check ✅ Passed The description matches the required template and includes summary, screenshots, testing, review, security, and notes.
Linked Issues check ✅ Passed The changes satisfy #8983 by classifying symlinked directories correctly in both browse paths and preserving Windows and local file-explorer behavior.
Out of Scope Changes check ✅ Passed The PR stays focused on symlink browsing fixes and regression tests, with no clear unrelated code changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@choihjin

Copy link
Copy Markdown
Contributor Author

Follow-up #8998 (stacked on this) adds a visual symlink indicator in the same browsers, so a linked directory is distinguishable from a real one — the natural next step now that they behave identically. It also extends ssh:browseDir to carry symlink state.

…ect browsers

The Add Project host folder browser classified a symlink-to-directory as
a file: browseServerDir used Dirent.isDirectory(), which does not follow
symlinks, and the SSH browse command relied on `ls -p`, which does not
dereference links. Classify symlink entries by their target type (stat
for the local/server listing, `[ -d ]` for the remote POSIX listing) so
linked project folders can be descended into and selected.

Fixes stablyai#8983
@choihjin
choihjin force-pushed the fix/add-project-browser-symlink-dirs branch from 58a5c33 to 81d99a3 Compare July 16, 2026 08:31
@brennanb2025

Copy link
Copy Markdown
Contributor

Taking a look!

@nwparker

Copy link
Copy Markdown
Contributor

Thank you @choihjin. Your approach (follow symlinks with stat on the server listing, and [ -d ] instead of ls -p on the SSH listing) is carried forward in #29658, ported onto the modules this code has since moved to, with you credited as co-author. Closing this one in favour of that PR.

@nwparker nwparker closed this Oct 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Symlinked directories are shown as files in the Add Project folder browser

3 participants