Skip to content

file:// hyperlinks with percent-encoded non-ASCII characters open the wrong path (each %xx is decoded as a separate character) #20803

Open

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

  1. 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

  2. Ctrl+click link A, then link B.

  3. 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

  1. added
    Needs-TriageIt's a new issue that the core contributor team needs to triage at the next triage meeting
    Needs-Tag-FixDoesn't match tag requirements
    on Oct 14, 2026
  2. changed the title [-]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)[/+] on Oct 14, 2026
  3. lhecker commented on Oct 14, 2026

    @lhecker
    Member

    When you construct an Uri class it decodes each byte (!) individually (!) to wchar_t. So, you can't use Uri::UnescapeComponent, because by the time you call it, the Uri object already destroyed the UTF8 encoding.

    ?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!?!

    WHAT THE FUCK

    ...

    LOL!

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

    Needs-Tag-FixDoesn't match tag requirementsNeeds-TriageIt's a new issue that the core contributor team needs to triage at the next triage meeting

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions