Skip to content

Terminal: Windows file links miss backslash relative paths and case- or slash-variant home folders #231

Closed

Description

@mubeda

Summary

In the Terminal, Ctrl-click on a Windows-style relative path does nothing, because it isn't detected as a link. Compilers and test runners on Windows print these all the time (src\index.ts:12, .\src\index.ts:12:7, ..\lib\a.ts:3). ~/… paths also fail to expand when the working directory is a Windows home path written in lower case (c:\users\jane\…) or with forward slashes (C:/Users/Jane/…). A quoted path with spaces is cut at the first space.

T3 Code fixed the same parser in pingdotgg/t3code#21199 and #19627. BiBCode's terminal-links.ts has the same patterns they changed.

Current behavior (verified at a0ea849ef92b)

Detection (apps/web/src/terminal-links.ts)

  • FILE_PATH_PATTERN at :41-42:
    /(?:~\/|\.{1,2}\/|\/|[A-Za-z]:[\\/]|\\\\)[^\s"'`<>]+|[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)+(?::\d+){0,2}/g
    The ~, ./ and ../ prefixes and the bare relative form (dir/file) accept only /. Drive-letter paths (C:\…, C:/…) and UNC paths already work.
  • The file is imported by the find projection worker (file header), so it has to stay dependency-free.
  • The link provider is registered in apps/web/src/components/ThreadTerminalPanel.tsx:1715.

Resolution

  • resolvePathLinkTarget (:381-398) expands ~/ only (not ~\) through inferHomeFromCwd.
  • inferHomeFromCwd (:142-160) matches a Windows home with /^([A-Za-z]:\\Users\\[^\\]+)/ at :153. That is case-sensitive (Users) and backslash-only.
  • Helpers: isWindowsAbsolutePath (:122-124), isWindowsPathStyle (:130-132), joinPath (:134-140). packages/shared/src/path.ts:28-32 has a related isAbsoluteHostPath.

Reproduced by running the module at a0ea849ef92b under Node 24 (extractTerminalLinks and resolvePathLinkTarget):

Input Today Expected
src\index.ts:12 no link src\index.ts:12
.\src\index.ts:12:7 no link .\src\index.ts:12:7
..\src\index.ts:12 no link ..\src\index.ts:12
C:\Users\Jane\p\i.ts:12 C:\Users\Jane\p\i.ts:12 same
"C:\Users\Jane Doe\p\i.ts:12" C:\Users\Jane (cut at the space) C:\Users\Jane Doe\p\i.ts:12
src/index.ts:12 src/index.ts:12 same
~/x.ts in C:\Users\Jane\proj C:\Users\Jane\x.ts same
~/x.ts in c:\users\jane\proj ~/x.ts (not expanded) c:\users\jane\x.ts
~/x.ts in C:/Users/Jane/proj ~/x.ts (not expanded) C:/Users/Jane/x.ts (separator: see Open questions)
~\x.ts in C:\Users\Jane\proj C:\Users\Jane\proj\~\x.ts (wrong file) C:\Users\Jane\x.ts

Existing tests – apps/web/src/terminal-links.test.ts: "finds Windows absolute paths with forward slashes" (:62), "expands home paths for macOS, Linux, and Windows cwd styles" (:292), "joins relative Windows paths and trims existing cwd separators" (:305). None of them cover backslash relative paths, ~\, a lower-case or forward-slash Windows home, or quoted paths with spaces.

Steps to reproduce

  1. On a Windows environment, open a Terminal in a project.
  2. Run echo src\index.ts:12 (or any tool that prints a relative backslash path).
  3. Hover and Ctrl-click the path.

Expected: it's underlined and opens src\index.ts at line 12 in the editor.
Actual: it isn't a link. The same happens for .\… and ..\…. ~\… resolves under the working directory instead of the home.

Proposed change

  1. Accept both separators in relative forms (as T3 #21199):
    /(?:~[\\/]|\.{1,2}[\\/]|\/|[A-Za-z]:[\\/]|\\\\)[^\s"'`<>]+|[A-Za-z0-9._-]+(?:[\\/][A-Za-z0-9._-]+)+(?::\d+){0,2}/g
  2. Quoted paths with spaces. Add a quoted-path pattern that keeps the whole path between matching quotes, plus a :line[:col] suffix inside or outside the quotes. A quoted match wins over a partial unquoted token. Keep the range correct across wrapped rows (resolveWrappedTerminalLinkRange).
  3. Home detection, case-insensitive, both slashes (as T3 #19627): /^([A-Za-z]:[\\/]Users[\\/][^\\/]+)/i. Expand ~\ as well as ~/ when the cwd is Windows-style.
  4. Relative backslash paths resolve against the cwd with joinPath, the same as / paths today. .\ and ..\ segments are kept as they are, like ./ and ../ today.
  5. Keep the module dependency-free.

T3 Code reference (their claims, not measured by us)

  • pingdotgg/t3code#21199, "recognize Windows terminal file links" (merged 2026-10-18). They report that src\index.ts:12 and .\src\index.ts:12 didn't match and that "C:\Users\Jane Doe\project\index.ts:12" was cut to C:\Users\Jane. Their fix: the regex above, a QUOTED_FILE_PATH_PATTERN with precedence over partial tokens, ~\ expansion, and it.each cases (src\index.ts:12, .\src\index.ts:12:7, ..\src\index.ts:12, C:\Users\Jane\project\index.ts:12, quoted paths).
  • pingdotgg/t3code#19627, "match Windows home paths case-insensitively in file links" (merged 2026-10-18): the home regex above, in packages/shared/src/fileLinks.ts.

Acceptance criteria

  • Table-driven unit tests in apps/web/src/terminal-links.test.ts (it.each), run from apps/web with vp test run --project unit src/terminal-links.test.ts:
    • extractTerminalLinks: every row of the table above, plus ..\..\a\b.ts:1:2, src\a.ts:12: (trailing colon trimmed), (src\a.ts:12) (delimiters trimmed), '.\a b\c.ts', a backtick-quoted path, a line with a URL and a backslash path (both found, no overlap), and \\server\share\a.ts (still detected, and still refused as a network path on activation);
    • resolvePathLinkTarget: ~/ and ~\ with C:\Users\Jane\…, c:\users\jane\…, C:/Users/Jane/… and c:/USERS/jane/…; .\src\a.ts:3 and src\a.ts against C:\repo and C:\repo\; POSIX home and relative cases unchanged.
    • With only terminal-links.ts reverted, the new cases fail.
  • Wrapped lines: a quoted path with spaces that wraps across two rows maps back to the right buffer range (a resolveWrappedTerminalLinkRange test).
  • Existing behavior unchanged: all current terminal-links.test.ts cases, URL detection, file:// handling, OSC 8 links, and the network-path refusal ("Network paths can't be opened from the terminal.").
  • Desktop e2e: vp run test:ui:desktop, including apps/desktop/e2e/specs/project-session-terminal.e2e.ts and terminal-font.e2e.ts. If the Windows native desktop job can run a terminal link check, add the src\index.ts:12 case there; otherwise say so in the PR.
  • Gates: vp check (zero warnings), vp run typecheck, Release Smoke. No Rust or contract changes are expected; if any appear, cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace and vp run check:contracts too.
  • UI.md and vercel-react-best-practices reviews are recorded. docs/testing/ runbooks are reviewed (cross-platform-validation.md:3632-3645, terminal links) and updated or stated "remain accurate".
  • Profiler coverage line in the delivery report. Terminal content and paths never reach profiler records or logs.

Living docs to update

  • docs/user/workspace-ui.md:1495-1508 (Terminal links): Windows relative paths, ~\, quoted paths with spaces.
  • docs/testing/cross-platform-validation.md:3632-3645: add a Windows relative-path row to the manual check.

Not in scope

  • Chat and Markdown file links (a different parser).
  • Opening network paths; they stay refused.
  • Normalizing .\ / ..\ before opening.

Related

  • None found in BiBCode's open or closed issues (searched for terminal link, Windows path, backslash).

Open questions

  • Should a bare dir\file be linked when the cwd is POSIX? It's a legal but rare filename there; T3 links it everywhere.
  • For a forward-slash Windows home (C:/Users/Jane), which separator should the expanded path use? isWindowsPathStyle treats C:/… as Windows style, so joinPath would produce a mixed C:/Users/Jane\x.ts. Keeping the home's own separator seems safer.

Activity

  1. added
    bugSomething is broken or behaving incorrectly.
    on Oct 31, 2026
  2. mubeda commented on Oct 31, 2026

    @mubeda
    OwnerAuthor

    Triage, 2026-10-31. Not going into PR #213 (#139 MCP batch). It goes in a follow-up PR after that release. Batch 2 (quick fixes): small and independent of #213.

    Planned order after the release: (1) #222 copy lint; (2) quick fixes #228, #221, #229, #231, #232; (3) MCP #220; (4) performance #225, then #223, #226, #227; (5) features #230, #224, #211, #212; (6) #210 after a Windows repro.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions