bug: file:// links with spaces in path render as plain unstyled text

Open 💬 3 comments Opened Mar 14, 2026 by aamodbhatt

Summary

file:// links whose path contains a literal space render as plain unstyled text in the TUI transcript instead of showing the resolved file path with cyan styling.

Root cause

In codex-rs/tui/src/markdown_render.rs, parse_local_link_target calls Url::parse on the raw destination string:

if dest_url.starts_with("file://") {
    let url = Url::parse(dest_url).ok()?;   // ← fails silently on spaces

Url::parse rejects URLs that contain literal spaces and returns an Err. The .ok()? propagates that as None, so render_local_link_target returns None.

Back in push_link, this means:

  • show_destination = false (it IS a local path per is_local_path_like_link)
  • local_target_display = None (parse failed)

In pop_link, neither rendering branch fires — the link label text is emitted as-is with no style and no destination shown. The result looks like plain text.

When does this happen?

CommonMark disallows bare spaces in link destinations, but allows them when the destination is wrapped in angle brackets:

[my file](<file:///Users/alex/projects/my project/README.md>)

Models (and editors) generating links to paths with spaces use this syntax. The space reaches parse_local_link_target literally and breaks the URL parser.

Reproduction

let text = render_markdown_text_for_cwd(
    "[my file](<file:///Users/example/code/codex/my file.md>)",
    Path::new("/Users/example/code/codex"),
);
// Currently renders as plain "my file" text instead of styled "my file.md"

Fix

Percent-encode spaces before calling Url::parse. The url crate's to_file_path() decodes %20 back to a space in the output, so the displayed path is correct:

let url = if dest_url.contains(' ') {
    Url::parse(&dest_url.replace(' ', "%20")).ok()?
} else {
    Url::parse(dest_url).ok()?
};

I have a branch with the fix + regression test ready if this is a good direction.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗