VSCode extension: chat file links with non-ASCII (CJK) or space-containing paths never open (webview percent-encodes href, host never decodes)
Summary
In the VSCode extension's chat webview, markdown file links whose workspace-relative path contains non-ASCII characters (e.g. CJK) or spaces are dead: clicking them does nothing, with no error surfaced. The webview percent-encodes the href during markdown rendering, but the extension host never decodes it before resolving the path.
This affects any non-English workspace (Chinese/Japanese/Korean folder or file names, accented Latin, and also plain ASCII paths containing spaces).
Environment
- Extension:
anthropic.claude-code2.1.206 (win32-x64) — also reproduced on 2.1.204 - VSCode stable, Windows 11
- Note: the extension's own system prompt instructs the model to emit workspace-relative markdown links (
[filename.ts](src/filename.ts)), so the model produces exactly the links that break.
Repro
- In a workspace, create a file whose relative path contains CJK, e.g.
文件/報告.md. - Have Claude output a markdown link in chat:
[報告.md](文件/報告.md). - Click the rendered link.
- Nothing opens. No toast/notification. (DevTools console of the extension host shows the path resolution attempt only.)
ASCII-only relative links (e.g. [PRD.md](PRD.md)) open fine.
Root cause (from reading the shipped bundles)
- webview/index.js — chat markdown is rendered with react-markdown. The remark/rehype pipeline's
normalizeUri(micromark-util-sanitize-uri) percent-encodes non-ASCII characters in link destinations, so the anchor'shrefprop becomes%E6%96%87%E4%BB%B6/%E5%A0%B1%E5%91%8A.md. - The custom anchor's
onClickruns the href through a "looks like a file path" parser (regex^([^:#]+?)(?:[:#]L?(\d+)(?:-L?(\d+))?)?$plus extension/prefix heuristics). The encoded string still passes (ends with.md), and is sent to the host asopen_file/filePath— still encoded. - extension.js — the
open_filehandler resolves relative paths withpath.join(workspaceFolders[0].uri.fsPath, filePath)and thenvscode.workspace.fs.stat(...). There is nodecodeURIComponentanywhere on this path (the only occurrences in extension.js belong to vendored libs — ajv, uri-js, axios). The literal%E6%96%87%E4%BB%B6directory does not exist,statthrows ENOENT, and the failure is swallowed silently.
Two adjacent issues worth noting:
- Absolute Windows paths can never open via chat links either: the file-link regex treats the drive-letter colon (
c:\...) as a line-number separator, so the match fails and the click is ignored. So there is no absolute-path workaround. - Silent failure: when
statfails, nothing is surfaced to the user — a toast ("file not found: …") would have made this diagnosable without reading the bundle.
Suggested fix
Decode before resolving in the open_file handler, e.g.:
try { filePath = decodeURIComponent(filePath) } catch {}
(or decode in the webview before sendRequest({type:"open_file", ...})), plus optionally surface a notification when the resolved path does not exist.
Thanks! The extension is otherwise working great for CJK-heavy workspaces — this is the one place where non-ASCII paths consistently fall through.