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
- Open Add Project.
- Browse to the home directory (
~/).
link-to-real does not appear in the listing. /tmp/real browsed directly
does appear.
- 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
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.
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,
~/Projectsis a symlink to/work/projects(a mounteddrive). When I open Add Project and browse my home directory, the
Projectssymlink 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, insidebrowse():readdir(..., { withFileTypes: true })returns dirents withlstatsemantics:for a symlink,
dirent.isDirectory()describes the link itself, not its target,and is therefore
false. A symlink pointing at a directory is classified asneither 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.browseRPC(
packages/contracts/src/rpc.ts:224,WsFilesystemBrowseRpcat line 674), andbrowse()above is its only implementation. The client(
packages/client-runtime/src/state/filesystem.ts,filterFilesystemBrowseEntries) filters only on the name prefix, so alldirectory 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 keepisSymbolicLink()dirents whosestat()target is a directory (guardingELOOP/ENOENTfor broken or cyclic links, which should be excluded). Oneextra
stat()per symlink candidate, on an interactive-only path. No contractchange is required —
FilesystemBrowseEntryis{ name, fullPath }andfullPathshould stay the symlink path the user typed, not the resolved target.This is still present on
mainas of 2026-08-27: line 222 is unchanged.Steps to reproduce
On the machine running the server:
~/).link-to-realdoes not appear in the listing./tmp/realbrowsed directlydoes appear.
~/linkproduces no completion.Reproducible every time. Platform-independent — nothing macOS-specific about it.
Version
0.0.35 (server
t3@0.0.35, launched asnpx 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
Nothing appears in
server.logfor these calls — there is no failure to log.Related issues
Its PR Show symlinks and macOS Finder aliases in folder browser #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 [Feature]: Add-project path autocomplete omits symlinked directories and macOS Finder aliases #2135 — symlinks only, no macOS Finder aliases, no
contract or icon changes.
discussion on 2026-08-15. Different cause (mount points, not symlinks), same
user-facing shape of "a valid directory is unreachable through the picker".
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/...symlinkpath, or add the project on the server machine directly.
Filed by
claude (Opus 5) via
t3 triage.