feat(web): render Codex file-citation chips - #6103
Conversation
Codex's artifact skills cite the files they write with a
`:codex-file-citation{path="..." purpose="..."}` directive, which nothing
in the renderer parsed, so the whole directive showed up as literal text.
A remark plugin rewrites each directive into a link node, which lands it
on the file chip Markdown file links already get — same path resolution,
same open-in-editor and preview behavior. A directive is only rewritten
when its path resolves to a file, so half-streamed and non-file
directives keep reading as text; code spans and fences are untouched.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7847f539bc
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const path = CITATION_PATH_ATTRIBUTE_PATTERN.exec(attributes)?.[1] | ||
| ?.replace(MARKDOWN_ESCAPE_PATTERN, "$1") | ||
| .trim(); |
There was a problem hiding this comment.
Preserve native Windows separators in citation paths
When Codex emits a native Windows path containing consecutive backslashes or a separator before punctuation, this replacement treats that separator as a Markdown escape. For example, \\server\share\report.docx is reduced to a single leading slash and no longer resolves as UNC, while C:\repo\.env becomes C:\repo.env and opens the wrong file. Parse or protect the directive before CommonMark escape processing instead of stripping every backslash before ASCII punctuation.
Useful? React with 👍 / 👎.
| for (const match of value.matchAll(CODEX_FILE_CITATION_PATTERN)) { | ||
| const path = readCitationPath(match[1] ?? ""); | ||
| if (!path) continue; | ||
| const href = codexFileCitationHref(path); | ||
| const fileLinkMeta = resolveMarkdownFileLinkMeta(href, cwd); |
There was a problem hiding this comment.
Preserve entity-like substrings in cited filenames
When a filename contains a CommonMark entity sequence such as /tmp/report&notes.pdf, the Markdown parser decodes the text node to /tmp/report¬es.pdf before this rewrite, while extractCodexFileCitationPaths scans the raw message and stores metadata for the original path. The generated href therefore misses the metadata map and targets a different filename, rendering as an ordinary link instead of an openable file chip. Citation directives need to be parsed from their raw source or otherwise protected from entity decoding.
Useful? React with 👍 / 👎.
| const markdownRemarkPlugins = useMemo<NonNullable<ReactMarkdownOptions["remarkPlugins"]>>( | ||
| () => [ | ||
| ...(lineBreaks ? CHAT_MARKDOWN_REMARK_PLUGINS_WITH_BREAKS : CHAT_MARKDOWN_REMARK_PLUGINS), | ||
| [remarkCodexFileCitations, { cwd }], | ||
| ], |
There was a problem hiding this comment.
Normalize citation directives in the mobile thread feed
This installs the citation transformation only in the web ChatMarkdown pipeline. The mobile assistant feed still passes message.text unchanged to SelectableMarkdownText or Markdown in apps/mobile/src/features/threads/ThreadFeed.tsx:966-982, so mobile users see the raw :codex-file-citation{...} directive and cannot open the generated artifact even though mobile already supports Markdown file links. Apply equivalent normalization in the shared/mobile rendering path.
AGENTS.md reference: AGENTS.md:L67-L71
Useful? React with 👍 / 👎.
ApprovabilityVerdict: Needs human review 1 blocking correctness issue found. This PR introduces new user-facing functionality (Codex file-citation chip rendering) which warrants human review. Additionally, a High severity finding about path parsing truncation and multiple other open comments identify substantive correctness issues with Windows paths and entity handling. You can customize Macroscope's approvability policy. Learn more. |
Markdown spends a text node's backslash escapes and character references before the citation plugin sees it, so the path in the tree was no longer the path Codex wrote: `C:\repo\.env` arrived as `C:\repo.env`, `\\server\share\report.docx` lost the pair that makes it UNC, and `report&notes.pdf` decoded to a different filename — each of which opens the wrong file, or no file at all. The path is now read from the source each text node was parsed from, with the parsed value only saying where in the node the directive sits, so the plugin runs before anything that rewrites text nodes and drops the position that source is found by. `codexFileCitationHref` also has no href for a path `encodeURI` refuses: an unpaired surrogate threw a URIError out of render. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| readonly cwd?: string | undefined; | ||
| } | ||
|
|
||
| function readCitationPath(attributes: string): string | null { |
There was a problem hiding this comment.
🟠 High src/markdown-codex-file-citations.ts:54
readCitationPath truncates a path that contains an escaped quote followed by text that looks like an attribute. For example, path="/tmp/report \" purpose=notes.pdf" purpose="output" is a valid quoted path containing " purpose=, but the regex stops at the escaped quote and reads the path as /tmp/report \, so the citation chip targets the wrong local path instead of the cited file.
This happens because CITATION_PATH_ATTRIBUTE_PATTERN matches lazily up to a quote that is followed by another attribute or end-of-directive, but the lookahead (?=\s*$|\s+[A-Za-z_][\w-]*=) can be satisfied by the text after an escaped interior quote, treating it as the closing delimiter. A fix would make the regex skip over \" sequences before testing for the real closing quote, or otherwise match the path without confusing escaped quotes with the terminator.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/markdown-codex-file-citations.ts around line 54:
`readCitationPath` truncates a path that contains an escaped quote followed by text that looks like an attribute. For example, `path="/tmp/report \" purpose=notes.pdf" purpose="output"` is a valid quoted path containing `" purpose=`, but the regex stops at the escaped quote and reads the path as `/tmp/report \`, so the citation chip targets the wrong local path instead of the cited file.
This happens because `CITATION_PATH_ATTRIBUTE_PATTERN` matches lazily up to a quote that is followed by another attribute or end-of-directive, but the lookahead `(?=\s*$|\s+[A-Za-z_][\w-]*=)` can be satisfied by the text after an escaped interior quote, treating it as the closing delimiter. A fix would make the regex skip over `\"` sequences before testing for the real closing quote, or otherwise match the path without confusing escaped quotes with the terminator.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0584217630
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const fileLinkMeta = resolveMarkdownFileLinkMeta(href, cwd); | ||
| if (!fileLinkMeta) continue; |
There was a problem hiding this comment.
Resolve explicit citations without Markdown path heuristics
When a directive names a valid path that the generic Markdown-link heuristic does not recognize—such as path="Makefile", path="/data/output", or the Windows-relative path="output\\report.docx"—resolveMarkdownFileLinkMeta returns null, so the directive remains raw text instead of becoming a chip. These paths are already explicitly identified as filesystem paths by the citation syntax; resolve them directly against cwd (while separately rejecting URLs) rather than requiring the narrower link-shape patterns in markdown-links.ts.
Useful? React with 👍 / 👎.
Render
:codex-file-citation{path="..." purpose="..."}directives in assistant messages as clickable local file chips, reusing the existing Markdown file-link resolution + open/preview, instead of raw markup. Directives inside code spans/blocks are left untouched; relative/absolute/Windows paths supported. Tests included (11 passing).Closes #5813.
Note
Render Codex file-citation chips in
ChatMarkdown:codex-file-citation{...}directives in AST text nodes into link nodes, using the file basename as the label.codexFileCitationHrefto encode cited paths as URL-safe hrefs, andextractCodexFileCitationPathsto scan raw message text for cited paths.Macroscope summarized 0584217.
Note
Low Risk
Scoped to chat Markdown rendering and reuses existing file-link open/preview paths; malformed or non-file citations are left as text.
Overview
Assistant messages can now turn
:codex-file-citation{path="..."}directives into the same clickable local file chips used for ordinary Markdown file links, instead of showing raw directive text.A new
remarkCodexFileCitationsplugin rewrites each directive into a link node when the path resolves via existingresolveMarkdownFileLinkMetalogic. Paths are read from the original source slice (so Windows backslashes and&in filenames are not mangled by Markdown parsing), with safe href encoding for drive letters and URL-special characters.ChatMarkdownruns this plugin beforeremarkBreaks(so hard-break splitting does not strip source positions) and pre-indexes citation paths for file-link metadata alongside normal link hrefs.Directives stay plain text when they are incomplete, in code, inside link labels, non-local (e.g.
https://), or unopenable; broad tests cover POSIX/Windows/UNC paths and chatlineBreaksmode.Reviewed by Cursor Bugbot for commit 0584217. Bugbot is set up for automated code reviews on this repo. Configure here.