Skip to content

fix(vscode): make markdown file links in assistant output clickable#2204

Open
B143KC47 wants to merge 4 commits into
MoonshotAI:mainfrom
B143KC47:fix/vscode-markdown-file-links
Open

fix(vscode): make markdown file links in assistant output clickable#2204
B143KC47 wants to merge 4 commits into
MoonshotAI:mainfrom
B143KC47:fix/vscode-markdown-file-links

Conversation

@B143KC47

@B143KC47 B143KC47 commented Jul 26, 2026

Copy link
Copy Markdown

Related Issue

Resolve #2194

Problem

Markdown links in assistant output that point at local files are never clickable in the VS Code extension — only https:// links work. Three gaps stack up: react-markdown's URL sanitizer empties file:// / vscode://file/ hrefs, the webview renders every link as a plain <a> (which the webview only follows for external schemes), and the openFile bridge rejects absolute paths and :line suffixes anyway.

What changed

  • New pure helper parseFileLink (webview lib/link-utils.ts): classifies an href as a local file target (plain relative/absolute path — including root-level ones like README.md:5file://, vscode://file/, native Windows path, each with an optional trailing :line(:col) suffix) or an external link. A :0 suffix is kept as part of the path rather than becoming an invalid line reference.
  • The Markdown a renderer routes file targets through the existing FileLinkbridge.openFile path (external links keep the anchor), and a custom urlTransform preserves exactly the hrefs parseFileLink accepts — anchor hrefs only, so image/media src attributes keep the default sanitizer behavior, and javascript: URLs are still stripped.
  • openFile gains an optional validated line param, and the handler now accepts absolute paths by relativizing them against the working directory before the existing containment check. Out-of-workspace paths still return ok: false, and a line is revealed via the vscode.open selection.

Review follow-ups included: Windows vscode://file/C:/... URIs drop the bogus leading slash, authority-bearing file://server/share/... URIs map to their UNC path (with localhost meaning no authority), drive-relative C:foo inputs stay refused instead of being classified absolute, #fragments are split off the path (GitHub-style #L20 reads as a line reference), the vscode://file authority boundary is enforced (vscode://fileevil/... is not a file link), and line references must be safe integers on both the parser and the bridge validator so an oversized :line suffix degrades to a plain path link instead of a rejected request.

Scope note: bare absolute paths in plain text (outside a markdown link) remain non-clickable; text enrichment still only links workspace-relative paths. If that variant should also be linkified, it is better tracked as a separate enhancement.

Tests: file-link-parsing.test.ts covers every href variant from the issue plus the Windows/UNC/fragment/authority/oversized-line cases and external-scheme passthrough; workspace-paths.test.ts gains absolute-inside (opens), absolute-outside (refused), drive-relative (refused), and line-selection cases. All fail without the fix. No changeset, matching previous extension-only fixes.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

B143KC47 added 2 commits July 26, 2026 17:11
Markdown links pointing at local files rendered as plain anchors, which
the webview never follows: react-markdown's sanitizer empties file:// and
vscode://file/ hrefs, and the openFile bridge rejected absolute paths and
:line suffixes anyway. Only http(s) links worked.

Route path-like hrefs (plain paths, file:// and vscode://file/ URIs, with
an optional trailing :line(:col) suffix) through the existing FileLink /
openFile bridge instead of an anchor, preserving those schemes with a
custom urlTransform. The openFile handler now accepts absolute paths that
resolve inside the selected working directory (relativized before the
existing containment check, so out-of-workspace paths still fail) and
reveals the requested line via the editor selection.

Bare absolute paths in plain text (outside a markdown link) are
unchanged; text enrichment still only links workspace-relative paths.

Fixes MoonshotAI#2194
…ransform to anchors

A root-level relative path with a line suffix (README.md:5) was read as
an unknown protocol by both the sanitizer pass-through and the file-link
parser, leaving those links dead. Strip the :line suffix before the
scheme test, preserve exactly what the parser accepts, and apply the
pass-through only to anchor hrefs so image sources keep the default
sanitizer behavior. A :0 suffix is kept as part of the path instead of
producing a line reference the bridge validation would reject.
Copilot AI review requested due to automatic review settings July 26, 2026 09:40
@changeset-bot

changeset-bot Bot commented Jul 26, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 4b526b9

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes VS Code extension chat Markdown rendering so assistant-emitted links to local files (including file:// / vscode://file/ and :line(:col) suffixes) become actionable by routing them through the extension host openFile bridge instead of relying on webview anchor navigation.

Changes:

  • Add parseFileLink + fileAwareUrlTransform to classify/preserve file-ish hrefs while keeping the default sanitizer for non-anchor URLs.
  • Render Markdown <a> elements as FileLink buttons when the href is a local file target, calling bridge.openFile(path, line?).
  • Extend the extension-host openFile handler to accept absolute paths (when inside the selected workdir) and optionally reveal a specific line; add unit tests for parsing + handler behavior.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
apps/vscode/webview-ui/tsconfig.json Includes the new parsing unit test in the webview-ui TS project.
apps/vscode/webview-ui/src/services/bridge.ts Extends openFile bridge API to accept an optional line.
apps/vscode/webview-ui/src/lib/link-utils.ts Adds file-link parsing + a urlTransform to preserve file-ish hrefs for anchors.
apps/vscode/webview-ui/src/components/Markdown.tsx Routes local-file markdown links through FileLink (bridge) and plugs in urlTransform.
apps/vscode/tsconfig.json Excludes the new parsing test from the extension TS project to avoid project conflicts.
apps/vscode/test/workspace-paths.test.ts Adds tests for absolute-path handling and line selection in openFile.
apps/vscode/test/file-link-parsing.test.ts Adds unit tests covering link parsing variants and sanitizer behavior.
apps/vscode/src/handlers/file.handler.ts Updates openFile to relativize absolute paths (when inside workdir) and support line reveal.
apps/vscode/shared/bridge.ts Validates the new openFile line param on the RPC boundary.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +34 to +36
} else if (VSCODE_FILE_URI.test(raw)) {
raw = raw.replace(VSCODE_FILE_URI, "");
} else if (EXTERNAL_SCHEME.test(raw)) {
Comment on lines +92 to +97
function isAbsolutePathInput(input: string): boolean {
// Same detection as resolveWorkspacePath's rejection branch.
return path.posix.isAbsolute(input.replaceAll("\\", "/"))
|| path.win32.isAbsolute(input)
|| /^[A-Za-z]:/.test(input);
}

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d43b205de7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

return path ? { path, line } : null;
}
}
return raw ? { path: raw } : null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Strip URL fragments from local file targets

When a local Markdown link includes a fragment, such as [Usage](README.md#usage) or file:///workspace/README.md#L20, this returns the entire href as the filesystem path. The extension consequently stats a literal file named README.md#usage, so the link remains unopenable; separate the fragment before returning the path and optionally interpret supported line fragments.

Useful? React with 👍 / 👎.

Comment on lines +31 to +33
if (FILE_URI.test(raw)) {
// `file:///C:/x` keeps `/C:/x` after the scheme — drop the extra slash.
raw = raw.replace(FILE_URI, "").replace(/^\/(?=[A-Za-z]:[\\/])/, "");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve authorities when parsing file URIs

For an authority-bearing file URI such as file://server/share/project/file.ts (the standard form for a Windows UNC path), stripping only the scheme turns it into the relative path server/share/project/file.ts. In a workspace on that share, openFile then resolves this underneath the workspace directory rather than to the linked UNC file and the click fails; parse the URI authority and convert it to the corresponding UNC path.

Useful? React with 👍 / 👎.

B143KC47 added 2 commits July 26, 2026 17:54
Review follow-ups: normalize the extra leading slash in Windows-drive
vscode://file/ URIs, map an authority-bearing file:// URI to its UNC
path instead of a workspace-relative one, split #fragments off the path
(reading GitHub-style #L20 as a line reference), and stop classifying
drive-relative inputs as absolute so they keep being refused.
… integers

Hardening from review: vscode://fileevil/... no longer classifies as a
file link (lookahead keeps the strip prefix-only), and line references
must be safe integers on both the parser and the bridge validator so an
oversized :line suffix degrades to a plain path link instead of a
rejected request.
@B143KC47

Copy link
Copy Markdown
Author

@codex review — all review findings are addressed as of 4b526b9: Windows vscode-file URIs, UNC file URIs, drive-relative refusal, fragment handling with #L20 line references, the vscode://file authority boundary, and safe-integer line validation on both sides of the bridge.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

Reviewed commit: 4b526b94c7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Markdown file links in assistant output are never clickable in the VS Code extension

2 participants