Repository navigation
file:// hyperlinks with percent-encoded non-ASCII characters open the wrong path (each %xx is decoded as a separate character) #20803
Description
Windows Terminal version
1.24.12741.0
Windows build number
10.0.26200.8894
Other Software
System locale: Chinese (ANSI code page 936). "Beta: Use Unicode UTF-8 for worldwide language support" is off. (The cause below does not appear to depend on the code page, but I have only tested this configuration.)
Steps to reproduce
-
Run in PowerShell 7 inside Windows Terminal:
$root = Join-Path ([IO.Path]::GetTempPath()) 'wt-link-repro' $ascii = Join-Path $root 'ascii'; $cjk = Join-Path $root '中文' New-Item -ItemType Directory -Force $ascii, $cjk | Out-Null Set-Content -LiteralPath "$ascii\a.txt" 'ascii' Set-Content -LiteralPath "$cjk\b.txt" 'cjk' function Get-FileUri([string]$p) { 'file:///' + ((($p -replace '\\','/') -split '/' | % { [Uri]::EscapeDataString($_) -replace '%3A',':' }) -join '/') } $u1 = Get-FileUri "$ascii\a.txt"; $u2 = Get-FileUri "$cjk\b.txt" "`e]8;;$u1`aA) ASCII path`e]8;;`a" "`e]8;;$u2`aB) CJK path`e]8;;`a" $u1; $u2
This prints two OSC 8 hyperlinks plus the raw URIs, e.g.
file:///C:/Users/<user>/AppData/Local/Temp/wt-link-repro/%E4%B8%AD%E6%96%87/b.txt -
Ctrl+click link A, then link B.
-
Ctrl+click the two raw URIs (detected by Terminal's URL detection).
Expected Behavior
Both files open in the default app (Notepad), since both URIs are valid RFC 8089 file URIs with UTF-8 percent-encoding.
Actual Behavior
- A (ASCII path): opens in Notepad.
- B (path containing
中文, percent-encoded as UTF-8): nothing happens. No process is started and no error dialog is shown. - Same result for the raw URIs: the ASCII one opens, the CJK one does nothing.
_OpenHyperlinkHandler (src/cascadia/TerminalApp/TerminalPage.cpp) passes the URI string unchanged to ShellExecuteW. Calling the same thing outside Terminal also fails:
Start-Process 'file:///C:/.../wt-link-repro/%E4%B8%AD%E6%96%87/b.txt'
# -> The system cannot find the file specified.The plain path C:\...\wt-link-repro\中文\b.txt opens fine, and so does the unescaped URI file:///C:/.../wt-link-repro/中文/b.txt.
The shell turns each %xx into a single character (byte value = code point, Latin-1 style) instead of decoding the bytes as UTF-8. I confirmed this by creating directories named after the possible mis-decodings:
| URI segment | File actually opened |
|---|---|
%E4%B8%AD%E6%96%87 (UTF-8 of 中文) |
directory 䏿–‡ (U+00E4 U+00B8 U+00AD U+00E6 U+0096 U+0087) |
%D6%D0%CE%C4 (GBK of 中文) |
directory ÖÐÎÄ (U+00D6 U+00D0 U+00CE U+00C4) |
中文 (not escaped) |
directory 中文 (correct) |
Tools that emit OSC 8 links use UTF-8 percent-encoding as RFC 3986/8089 require, so any link to a path with non-ASCII characters opens the wrong path or silently fails.
A possible fix: for local file: URIs, unescape the path as UTF-8 (e.g. Uri::UnescapeComponent) and pass a local Windows path to ShellExecuteW instead of the raw URI.
Possibly related: #14116 (other file:// URI forms that are not handled).
Edit: corrected the root-cause analysis. The first version said the shell decodes %xx with the ANSI code page; further testing (table above) showed it decodes each %xx as a separate character regardless of encoding.
Disclosure: I used an AI assistant to help investigate and draft this report. I reproduced all steps and results above myself.
Activity
[-]file:// hyperlinks to paths with non-ASCII characters do nothing when the system code page is not UTF-8[/-][+]file:// hyperlinks with percent-encoded non-ASCII characters open the wrong path (each %xx is decoded as a separate character)[/+]lhecker commentedon Oct 14, 2026
When you construct an
Uriclass it decodes each byte (!) individually (!) towchar_t. So, you can't useUri::UnescapeComponent, because by the time you call it, theUriobject already destroyed the UTF8 encoding.?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!
WHAT THE FUCK
...
LOL!