Skip to content

fix(projects): list symlinked folders as folders in the remote Add Project browser - #29658

Merged
nwparker merged 1 commit into
mainfrom
nwparker/symlink-dir-browse
Oct 16, 2026
Merged

nwparker merged 1 commit into
mainfrom
nwparker/symlink-dir-browse

Conversation

@nwparker

@nwparker nwparker commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 3 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​157 $\color{#cf222e}{\Huge{\mathbf{−}}}$​3 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​154
Prod 2 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​37 $\color{#cf222e}{\Huge{\mathbf{−}}}$​10 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​27

ELI5

When you add a project on a remote machine, Orca shows you that machine's folders. A shortcut (symlink) that points at a folder was shown as a plain file, so you could not open it or pick anything inside it. Now it shows up and opens like any other folder.

What Changed

Before: in Add Project for an SSH host or a managed Orca server host, linked -> /data/projects appeared among the files, with no folder icon, and clicking it did nothing. Anything that lived behind a symlinked path was unreachable from the picker.

After: a symlink to a folder is listed with the folders and you can open it and select projects inside it. A broken symlink, a symlink loop and a symlink to a file still appear as files, and none of them break the listing.

Mechanism, for both ways Orca lists a remote folder:

  • Managed server host (files.browseServerDir, runtime-server-environment-commands.ts): Dirent.isDirectory() is false for a symlink. For entries that are symlinks we now stat() the target and count it as a folder only if the target is a folder. Any stat error (missing target, ELOOP from a loop) leaves it as a file.
  • SSH host (ssh-remote-directory-browse.ts): the listing ran ls -1Ap, and -p marks only real directories with /. The command now captures ls -1A and adds the / with [ -d ], which follows symlinks and is false for broken links and loops. The output format the parser reads is unchanged. The ls output is captured into a variable first, so a directory that can be entered but not read still fails the command instead of looking empty (covered by a test).

The Windows PowerShell listing already used PSIsContainer, which follows links, so it is unchanged. The local and WSL "browse" buttons open the operating system's own folder picker, which already follows symlinks, so they do not have this bug. WSL hosts that are browsed in-app go through the managed-server path above.

Why

ls -L would also follow links, but GNU ls -L reports an error and exits non-zero on a broken symlink, so one dangling link would fail the whole listing. A per-entry [ -d ] test is POSIX, works with busybox and macOS sh, and costs one stat per entry. On the managed side we only stat entries that are symlinks, so a big folder costs nothing extra. Paths are not resolved to their real location, so the breadcrumb still shows the path the user navigated.

This takes over the approach from #8987 by @choihjin (credited as co-author). That PR targets files that no longer exist on main, so it is ported onto the current modules here.

Linked Issue

Fixes #29490
Fixes #8983

Visual Proof

N/A: no new UI. The existing entry list just classifies the entry correctly. The live check on a real Linux host is described under Testing.

Testing

  • src/main/ssh/ssh-remote-directory-browse.test.ts (new) runs the real generated command in /bin/sh against a temporary directory. It contains a symlink to a folder, a symlink to a file, a broken link, a two-link loop, a hidden file, a name with a space, an empty linked folder and an unreadable folder. It fails on main because linked-dir is listed as a file.

  • src/main/runtime/runtime-server-environment-commands.test.ts (new) uses the same fixture for the managed-server listing, and fails on main the same way.

  • src/main/ipc/ssh-browse.test.ts assertions were updated for the new command.

  • Live: verified on openclaw (Linux) under host isolation: a private user-level sshd and throwaway HOME in /tmp, an isolated hidden desktop built from this change, and a managed server auto-deployed from that build. The home folder held linked -> real/, repo-link -> repo/, a broken link and a two-link loop. In the real "Browse host filesystem" dialog (the managed-server listing), linked and repo-link were listed with the folders, clicking linked opened it and showed project-a, and the broken link and loop entries were listed as files without an error. The raw-SSH listing (ssh.browseDir over the live SSH connection to the same host) returned linked and repo-link as folders and the broken link and loops as files, and ~/linked listed project-a. On main, the soak run had reproduced the bug in the same dialog. Windows SSH hosts are not verified live, because their PowerShell path is unchanged.

  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

AI Disclosure

Review

Codex review (gpt-6.1-sol, high): no findings.

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

SSH and managed hosts are the main target. Windows SSH hosts (PowerShell) are unchanged. No wire change: the entry shape is the same, and only the isDirectory value is now correct.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

…oject browser

A symlink to a directory was listed as a file on SSH and managed hosts, so
projects behind a linked path could not be reached. Follow links when
classifying entries; broken links and loops stay files.

Co-authored-by: Gideon <jjin6573@naver.com>
@coderabbitai

coderabbitai Bot commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 94b2465c-e8ed-42d8-b3a8-41d0463eed40
📥 Commits

Reviewing files that changed from the base of the PR and between 942ffe2 and 5746d5d.

📒 Files selected for processing: 5
  • src/main/ipc/ssh-browse.test.ts
  • src/main/runtime/runtime-server-environment-commands.test.ts
  • src/main/runtime/runtime-server-environment-commands.ts
  • src/main/ssh/ssh-remote-directory-browse.test.ts
  • src/main/ssh/ssh-remote-directory-browse.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 2 remain after this review.

📝 Walkthrough

Managed and direct SSH directory browsing now identify symlinks to directories as directories. The managed browse path follows symlink targets with stat. The POSIX SSH command uses shell directory checks to add directory markers. Tests cover symlink types, navigation through directory symlinks, and listing errors.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 5746d

Remote browsing can show directory symlinks as folders. No actionable merge-blocking issue is established.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 5 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: remote Add Project browsers now list symlinked folders as folders.
Description check Passed The description follows the required template and explains the user impact, implementation, rationale, linked issues, visual proof, testing, platform scope, and checklist status. It also documents aut…
Linked Issues check Passed The changes satisfy #29490 by classifying POSIX SSH symlinks to directories as folders and keeping file symlinks, broken links, and loops as files. The managed-server path performs the target check an…
Out of Scope Changes check Passed The production changes are limited to SSH and managed-server directory classification. The added tests validate symlink classification, navigation, empty directories, and listing failures. The updated…
Full details: Description check

Explanation

The description follows the required template and explains the user impact, implementation, rationale, linked issues, visual proof, testing, platform scope, and checklist status. It also documents automated and live validation, including known Windows verification limits.

Full details: Linked Issues check

Explanation

The changes satisfy #29490 by classifying POSIX SSH symlinks to directories as folders and keeping file symlinks, broken links, and loops as files. The managed-server path performs the target check and treats stat errors as files. Added tests cover classification and navigation. The changes satisfy the remote portion of #8983. The PR evidence states that local and WSL browse buttons already use an operating-system picker that follows symlinks, so no local code change is required.

Full details: Out of Scope Changes check

Explanation

The production changes are limited to SSH and managed-server directory classification. The added tests validate symlink classification, navigation, empty directories, and listing failures. The updated IPC expectations preserve the existing entry format. These changes support #29490 and #8983.

  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@nwparker
nwparker merged commit c8ad8ea into main Oct 16, 2026
44 of 45 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

1 participant