Follow symlinks in Tab path completion (remote/SSH sessions) - #14746
Conversation
Symlinks pointing at directories were classified as files in two completer code paths, so they were not offered as directory completions: - Remote/Warpified sessions: the find script used to list a directory omitted -L, so a symlink-to-directory failed -type d and landed in the files bucket. Add -L to both find invocations. - WSL (and other emulated) sessions: the local listing follows a symlink via a host-side metadata() call, which fails when the target lives in the guest path space (e.g. /mnt/c/...). Fall back to resolving the link target through the session before deciding whether it is a directory. Plain local Linux/mac behavior is unchanged (CORE-3402). Broken links and loops still classify as non-directories, so completion never breaks. Adds remote and local symlinked-directory regression tests. Co-Authored-By: Warp Agent <agent@warp.dev>
Comments-only change (no behavioral or test-logic changes). Rewrites the
comments added for the symlink completion fix to follow AGENTS.md: drop
internal-step narration and transformation ("used to") phrasing, keep only
the non-obvious why, and reflow to the 100-column width.
Co-Authored-By: Warp Agent <agent@warp.dev>
Split per requester: this PR fixes remote/Warpified SSH symlink completion only. Remove the emulated-session (WSL, MSYS2) fallback and its tests; local macOS/Linux already follows symlinks via EngineDirEntry::try_from and is untouched. WSL remains open and is tracked on APP-3993. Co-Authored-By: Warp Agent <agent@warp.dev>
There was a problem hiding this comment.
Overview
This PR adds -L to both find invocations in ls_script_for_dir so remote / Warpified (SSH) Tab completion classifies a symlink-to-directory as a directory, plus a regression test that drives the real remote listing path. The code change is correct, tested, and CI-green for the remote/SSH scope; the only blocker is a scope decision that a human must make (below).
Concerns
❓ [QUESTION] Scope vs. the ticket's acceptance criteria. APP-5190's acceptance criteria include a WSL case ("in a WSL session, a symlinked directory — including targets under /mnt/c/... — tab-completes as a directory"), and the active reports on issue #4498 — including the requester who tested this branch — are on WSL. This PR intentionally fixes only remote/Warpified SSH sessions and defers WSL to APP-3993 (WSL resolves to SessionType::Local and lists via std::fs::read_dir over the \\wsl$\... host view, so -L does not reach it). That descope is honest and clearly documented, but it means the reporter's own case is not fixed by this PR. Is landing the remote/SSH-only fix under APP-5190 (with WSL tracked separately on APP-3993) acceptable, or should APP-5190 remain open until the WSL path is also addressed? This is a scope/product decision, not a code change.
Verdict
Checks: build ✅ (CI) · tests ✅ (CI: Linux/macOS/Windows) · clippy+fmt ✅ (CI) · visual proof ✅ (remote/SSH, Warpified indicators shown) — local build/test gate not run: OOM-killed on the review runner (environment limitation, not the change).
Found: 0 critical, 0 important, 1 question
Request changes (blocked pending a human decision on scope; the delivered code has no code-level findings)
Review run
https://oz.staging.warp.dev/runs/019fd3e6-c83d-7cc9-b10a-733a1627cb0e
…n round-trip Reporter logs (from the instrumentation this branch added) established the mechanism: a WSL session lists via SessionType::Local, the entry is a symlink, but value.path().metadata() returns Err(NotFound) because the target is an IO_REPARSE_TAG_LX_SYMLINK the Windows host cannot follow. std::fs::read_link cannot read that reparse tag either (Rust std only handles IO_REPARSE_TAG_SYMLINK / MOUNT_POINT), so host-side resolution is impossible and unwrap_or(false) bucketed the symlink as a file. Fix (surgical, guest-side): after the host std::fs listing, if the session is emulated (WSL/MSYS2) and any symlink was left unclassified as a directory, ask the guest which immediate children are directories with `cd <dir> && find -L . -maxdepth 1 -type d -print0` (the same -L shape that fixed the remote path in #14746) and upgrade the matching symlink entries. Nothing from the listing is interpolated into the command, so there is no filename-quoting or injection surface. Latency: the command runs only for emulated sessions and only when an unresolved directory symlink is present; it is awaited on the async completion path (the sibling WarpifiedRemote branch already awaits a guest command here) and bounded with with_timeout, degrading to the host classification (symlink shown as a file) on timeout/error rather than stalling completion. Results are cached. Plain local macOS/Linux is unaffected. Keeps one temporary debug-gated read_link probe so the reporter's verification run also records read_link's actual result on their WSL host. Adds unit tests for the output parsing and the directory-symlink upgrade (the Err-from-metadata case); the local try_from behavior-preservation test is retained. Co-Authored-By: Warp Agent <agent@warp.dev>
Description
In a remote / Warpified (SSH) session, Warp lists a directory for path completion by running a
findscript (ls_script_for_dirinapp/src/completer/mod.rs). It usedfind . -maxdepth 1 -type dfor directories andfind . -maxdepth 1 -not -type dfor files.findwithout-Ldoes not follow symlinks when evaluating-type, so a symlink pointing at a directory failed-type dand was bucketed as a file — it was not offered as a directory completion (no trailing separator, excluded fromcddirectory completions). Adding-Lto both invocations makesfindfollow symlinks, so a symlink to a directory is classified as a directory (matching a standard terminal), and a symlink to a file still completes as a file.Scope: this PR fixes remote / Warpified SSH sessions only.
EngineDirEntry::try_from, which follows a symlink viapath().metadata()(CORE-3402). It is unchanged by this PR.SessionType::Local(same host, not SSH) and lists viastd::fs::read_dirover the\\wsl$\...host view, not via thisfindscript, so-Ldoes not affect it. WSL is tracked separately on APP-3993.Refs Follow symlinks with Tab autocomplete (Linux client) #4498 (partially — the remote/SSH portion).
Linked Issue
Refs #4498
ready-to-specorready-to-implement.Testing
test_session_context_follows_symlinked_directories_remotely, which builds a real directory, a symlink to it, a file, and a symlink to that file, then runs the actual remote listing (thefindscript via a bash subprocess). It fails before the-Lchange (the directory symlink is classified asFile) and passes after — both states confirmed../script/format --check,cargo clippy -p warp --all-targets --tests -- -D warnings, andcargo nextest run -p warp completer::all pass.ssh -p 2222 localhost), which Warp auto-Warpified into a remote session (confirmed by theagent@<host>user@host chip on the input line and the tab renamed to<host>:~, i.e. the session actually exercises the remotels_script_for_dirpath this PR changes). In~/symlinktest(containinglinkdir→realdir), typingcd linkand pressing Tab completed tocd linkdir/— the symlinked directory is offered as a directory (trailing separator, folder icon, "Directory" label), identical to the realrealdir/. See the recording and screenshots below.Screenshots / Videos
Computer-use video recordings
Warp SSH warpify & Tab-completion test: Launching Warp, running echo, connecting via loopback SSH on port 2222, checking for a Warpified/remote indicator, then testing Tab completion of "cd link" in ~/symlinktest.
Computer-use screenshots (3)
Warp after the loopback ssh -p 2222 localhost command: the input line shows an "agent@t72g7k6t7vtma" user@host chip, the tab is renamed "t72g7k6t7vtma:~", and the remote login banner rendered as a Warp block headed "agent@t72g7k6t7vtma:~".
Result of typing "cd link" and pressing Tab in the Warpified SSH session: it auto-completed inline to "cd linkdir/" (with trailing slash, treating the symlink as a directory); no popup list appeared because linkdir was the only directory match.
Warp "cd " + Tab completion menu in the SSH session showing two entries, "linkdir/" and "realdir/", each with a folder icon, trailing slash, and the label "Directory"; the symlink linkdir is presented identically to the real directory, while linkfile/realfile.txt are not offered by cd completion.
./script/runAgent Mode
CHANGELOG-IMPROVEMENT: Tab autocomplete now follows symlinks to directories in remote/SSH sessions.
Refs #4498
Originating thread: https://warpdev.slack.com/archives/C0BDQDW8V5E/p1785963086148259
Conversation: https://staging.warp.dev/conversation/a9e89e2e-4f9a-4b93-894b-d15a326cacb8
Run: https://oz.staging.warp.dev/runs/019fd3be-69c6-72ae-aea4-bdb5ba1ca004
This PR was generated with Oz.