pingdotgg / pingdotgg/t3code

Add Project browser and path autocomplete omit symlinked directories

Open
#8,408 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
23k
Forks
5.9k
Avg merge
11h 14m
Merged PRs (30d)
357

Description

What happened

The Add Project file browser and the path autocomplete do not show symlinks to
directories, so I could not browse into my projects at all.

On the server machine, ~/Projects is a symlink to /work/projects (a mounted
drive). When I open Add Project and browse my home directory, the Projects
symlink never appears as a folder, so none of my projects are reachable through
the UI. The only ways in are typing the fully resolved real path by hand, or
adding the project on the server machine directly.

Surface: desktop app 0.0.35 on macOS, connected via the SSH-launch flow to a
Linux server running t3@0.0.35.

Diagnosis

Single root cause in apps/server/src/workspace/WorkspaceEntries.ts:222, inside
browse():

for (const dirent of dirents) {
  if (
    dirent.isDirectory() &&
    dirent.name.toLowerCase().startsWith(lowerPrefix) &&
    (showHidden || !dirent.name.startsWith("."))
  ) {

readdir(..., { withFileTypes: true }) returns dirents with lstat semantics:
for a symlink, dirent.isDirectory() describes the link itself, not its target,
and is therefore false. A symlink pointing at a directory is classified as
neither a file nor a directory and is filtered out entirely.

The Add Project directory picker and the path autocomplete are the same code
path — both are served by the filesystem.browse RPC
(packages/contracts/src/rpc.ts:224, WsFilesystemBrowseRpc at line 674), and
browse() above is its only implementation. The client
(packages/client-runtime/src/state/filesystem.ts,
filterFilesystemBrowseEntries) filters only on the name prefix, so all
directory classification happens in that one server-side loop. That is why both
surfaces fail identically.

The call succeeds — it does not error. Entries are silently dropped, so nothing
surfaces in the logs and the directory just looks empty of that folder.

Suggested fix, matching the smallest-scope proposal in the earlier report: keep
dirents where isDirectory() is true, and additionally keep
isSymbolicLink() dirents whose stat() target is a directory (guarding
ELOOP/ENOENT for broken or cyclic links, which should be excluded). One
extra stat() per symlink candidate, on an interactive-only path. No contract
change is required — FilesystemBrowseEntry is { name, fullPath } and
fullPath should stay the symlink path the user typed, not the resolved target.

This is still present on main as of 2026-08-27: line 222 is unchanged.

Steps to reproduce

On the machine running the server:

mkdir -p /tmp/real
ln -s /tmp/real ~/link-to-real
  1. Open Add Project.
  2. Browse to the home directory (~/).
  3. link-to-real does not appear in the listing. /tmp/real browsed directly
    does appear.
  4. Same in the path autocomplete: typing ~/link produces no completion.

Reproducible every time. Platform-independent — nothing macOS-specific about it.

Version

0.0.35 (server t3@0.0.35, launched as npx t3); desktop app 0.0.35 on macOS.

Environment

Server: Linux x64 (kernel 7.1.9), Node v26.7.0, server running on
127.0.0.1:3773. Client: desktop app 0.0.35 on macOS, connected over the
SSH-launch flow.

Evidence

# Real symlinks on the server machine:
~/Projects  -> /work/projects
~/Worktrees -> /work/worktrees

# Node dirent semantics that browse() relies on:
link-to-real   dirent.isDirectory=false  dirent.isSymbolicLink=true  | stat(target).isDirectory=true
plaindir       dirent.isDirectory=true   dirent.isSymbolicLink=false | stat(target).isDirectory=true

# Applying the v0.0.35 filter vs. a symlink-aware filter to the home directory:
v0.0.35 browse("~/") returns:  Documents, Downloads, Music, Pictures, Videos
symlink-aware filter returns:  Documents, Downloads, Music, Pictures, Projects, Videos, Worktrees
MISSING:                       Projects, Worktrees

# server.trace.ndjson — the RPC succeeds; entries are dropped silently:
{"type":"effect-span","name":"WorkspaceEntries.browse","durationMs":0.457326,"exit":{"_tag":"Success"}}
{"type":"effect-span","name":"ws.rpc.filesystem.browse","attributes":{"rpc.method":"filesystem.browse",
 "rpc.aggregate":"workspace"},"durationMs":0.549747,"exit":{"_tag":"Success"}}
# (2026-08-27T14:33:04.409Z and 14:33:29.650Z)

Nothing appears in server.log for these calls — there is no failure to log.

Related issues

  • #2135 — same root cause and same line, filed 2026-04-17 as a feature request.
    Its PR #2136 was closed unmerged on 2026-07-17, and the issue was converted to
    a discussion and locked on 2026-08-15, so the report is no longer in the
    tracker and cannot be commented on. Refiling as a bug: this is not a missing
    enhancement but a directory that cannot be browsed at all. Scope here is
    deliberately narrower than #2135 — symlinks only, no macOS Finder aliases, no
    contract or icon changes.
  • #6023 — external drives missing from the directory picker; also converted to a
    discussion on 2026-08-15. Different cause (mount points, not symlinks), same
    user-facing shape of "a valid directory is unreachable through the picker".
  • #8234 (open) — symlinks to iCloud Drive inside an already-added project fail on
    file reads and diffs. Different surface, plausibly the same lstat-vs-stat
    assumption elsewhere in the workspace layer.

Fix applied or workaround

Nothing was changed on the machine. Workaround: type the resolved real path
(/work/projects/...) in the picker instead of the ~/Projects/... symlink
path, or add the project on the server machine directly.

Filed by

claude (Opus 5) via t3 triage.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in apps/server/src/workspace/WorkspaceEntries.ts at browse(), then trace the filesystem.browse RPC described in packages/contracts/src/rpc.ts and WsFilesystemBrowseRpc. Reproduce with a directory symlink from the issue; done means symlinked directories appear in both browsing and autocomplete while broken or cyclic links remain excluded.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, typescript
Domain
backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.