fix(server): symlinked directories show up in the path picker#8281

closed4
main
GitHub fork: fix/browse-symlinked-directories
openedAug 26, 2026byPPengLx
View on GitHubTwo-way sync enabledPR last imported 15d ago

What Changed

WorkspaceEntries.browse — the single server-side implementation behind the web command palette / add-project picker and mobile's AddProjectScreen — filtered readdir entries with dirent.isDirectory(). readdir reports a symlink by its own type and never its target's, so every symlink pointing at a directory was silently dropped from the listing.

Candidates now include symlinks, and each link gets one follow-the-link stat before it is offered as browsable. Broken links, links to files, and unreadable targets still drop out, so the visible set is unchanged apart from the symlinked directories that were missing. Plain directories pay no extra syscall, and the stat calls only happen for symlinks that already passed the name-prefix and hidden-file filters (concurrency capped at 16).

No contract change — FilesystemBrowseEntry is still { name, fullPath }, and the client-side filterFilesystemBrowseEntries filters by name only.

Why

Reported on Linux: picking a project path that goes through a symlinked directory is impossible in the UI. The link never appears in the dropdown, so you have to type the path past it by hand before autocomplete works again. Symlinked directories are everywhere on Linux — dotfile managers, /mnt mount layouts, pnpm store paths, worktree setups.

Repro:

1mkdir -p /tmp/real-project
2ln -s /tmp/real-project ~/linked-project

Then browse ~/ in the path picker: linked-project is absent before this change, present after.

UI Changes

No visual change — the same dropdown now lists entries it was incorrectly omitting.

Testing

Added follows symlinks that point at directories and skips the rest to WorkspaceEntries.test.ts, covering a symlinked directory (listed), a symlink to a file (skipped), and a broken symlink (skipped). Verified it fails against the old filter.

  • vp test run apps/server/src/workspace/WorkspaceEntries.test.ts — 31 passed
  • tsgo --noEmit in apps/server — no errors
  • vp lint apps/server/src/workspace/ — clean

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes (n/a — no visual change)
  • I included a video for animation/interaction changes (n/a)

🤖 Generated with Claude Code


Note

Low Risk Scoped to filesystem browse listing with the same API contract; extra stat calls only on symlink candidates that already passed filters.

Overview WorkspaceEntries.browse (path picker / add-project filesystem listing) no longer drops directory symlinks because readdir reports them as links, not directories.

Candidates now include directories and symlinks that match the prefix and hidden-name rules. Symlinks get a follow-up stat (up to concurrency 16); only those that resolve to directories are returned. Symlinks to files, broken links, and unreadable targets stay excluded. Real directories are unchanged—no extra syscall.

A new browse test covers a linked directory (listed), linked file (skipped), and broken link (skipped).

Reviewed by Cursor Bugbot for commit c603b86b47052e041ee3b4310500fdf9c7304ed0. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix symlinked directories not appearing in WorkspaceEntries.browse path picker

  • The browse handler previously only included direct subdirectories, skipping symlinks entirely. Now it collects both directories and symlinks matching prefix/hidden rules, then stat()'s each symlink to keep only those resolving to directories.
  • Broken links, unreadable targets, and symlinks to files are excluded. Symlink resolution uses Effect.forEach with concurrency 16.
  • Behavioral Change: symlinks to files or broken symlinks no longer appear in browse results; previously they were excluded by dirent.isDirectory() and now are explicitly rejected after stat.

Macroscope summarized c603b86.

Summary by CodeRabbit

  • New Features

    • Directory browsing now follows symbolic links that point to valid directories.
    • Symbolic links to files, broken links, and links with unreadable targets are excluded from directory results.
  • Tests

    • Added coverage confirming the updated symbolic-link browsing behavior.

Checks

7 successful · 11 skipped
All checks passed
a895286

Added 1 commit

  • fix(server): symlinked directories show up in the path picker2887da9
Synced from GitHub

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: pingdotgg/t3code/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: d93f296d-e40b-42a0-b669-3946db39de9a

📥 Commits

Reviewing files that changed from the base of the PR and between 7a326c244f72d6421c2f1cbf004d55806c631526 and a89528660e26b5387ea2e7d2259d23135be40501.

📒 Files selected for processing (2)
  • apps/server/src/workspace/WorkspaceEntries.test.ts
  • apps/server/src/workspace/WorkspaceEntries.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

WorkspaceEntries.browse now follows symlinks when checking directory entries. It retains symlinks to directories and skips symlinks to files, broken links, and unreadable targets. A test covers these cases.

Changes

Workspace browse symlink handling

Layer / File(s)Summary
Symlink filtering and validation
apps/server/src/workspace/WorkspaceEntries.ts, apps/server/src/workspace/WorkspaceEntries.test.ts
browse stats directory and symlink candidates concurrently and retains only directories. The test verifies that file and broken symlinks are excluded.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Suggested reviewers: juliusmarminge

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly and concisely describes the main change: directory symlinks now appear in the server path picker.
Description check✅ PassedThe description includes the required What Changed, Why, UI Changes, and Checklist sections. It explains the implementation, scope, testing, and the absence of visual changes. The documented follow-up…
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 2…
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

Warning

⚠️ This pull request shows signs of AI-generated slop (ai_padded_prose). It has been flagged by CodeRabbit slop detection and should be reviewed carefully.


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

Synced from GitHub

Approvability

Verdict: Approved at 2887da9

Macroscope's review found this PR approvable — This is a focused server-side bug fix that makes valid directory symlinks visible in the existing path picker while excluding broken links and file links. The response contract and ordinary-directory behavior remain unchanged, with targeted test coverage for the new cases.

No code changes detected at a895286. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

Note

🤖 GPT-6 Astra (preview) responding on behalf of Theo

This note is part of an automated cleanup pass.

Preserving these details from items reviewed in the cleanup pass.

Carryover from #9203 at bb4756e: preserve WorkspaceEntries.test.ts case includes symlinked directories and ignores non-directory targets. Its fixture has a real directory, a symlink to that directory, a symlink to a file, and a broken link. The result must include both directory entries, retain the symlink's own full path, and omit the file and broken targets. Apply it to the bounded-concurrency implementation without copying the sequential stat loop. This note records the fixture for follow-up and does not claim it was ported or run.

Preserve the explicit Windows junction fixture from #2798 at ae46c95a14. Create a real directory and a junction to it, then require browse to return both paths. Use the existing NodeFSP import rather than that patch's undefined fsPromises name. Also check a valid junction named Templates or Recent outside the user profile, so a legacy-name filter cannot hide it. This records test cases to retain, not a completed port or a Windows test run.

Added 3 commits

  • Merge branch 'main' into fix/browse-symlinked-directories2d3c920
  • Merge branch 'main' into fix/browse-symlinked-directoriesc603b86
  • Merge branch 'main' into fix/browse-symlinked-directoriesa895286
Synced from GitHub

Can't believe that in the LLM era, such a useful solution still hasn't been implemented. When I didn't see any symlinked dirs, I wanted to open my own MR, but found this one, which is over a month old. @t3dotgg, it's been about 26 days since the last commit.

Thank you @PengLx for finding and fixing this. pingdotgg/t3code#26168 carries the same picker fix forward on current main (this branch has merge conflicts), credits you, and adds the follow-up cases requested here plus network-link hardening. Closing this one in favour of that PR.