Markdown links in tables expand to full URL instead of showing short link text

Resolved 💬 3 comments Opened Mar 23, 2026 by gnodet Closed Mar 27, 2026

Description

When Claude outputs markdown links like [text](url) inside table cells, the link text is not displayed as a short clickable hyperlink. Instead, the full URL is shown (or the table falls back to card layout), losing the benefit of OSC 8 terminal hyperlinks.

Outside of tables, links render correctly as clickable text via OSC 8 escape sequences.

Steps to reproduce

Ask Claude to output a markdown table containing links, e.g.:

| Name | Link |
|------|------|
| Example | [click here](https://example.com/very/long/path/to/resource) |
| Docs | [documentation](https://docs.example.com/guide/getting-started) |

Expected: Table cells show short clickable text ("click here", "documentation")
Actual: The full URL is displayed, or the table switches to card/list layout because the OSC 8 escape sequences inflate the perceived column width

Root cause analysis

After investigating the minified source (cli.js from the npm package), here's what happens:

The rendering pipeline

  1. marked.lexer() parses markdown into tokens
  2. Tables are extracted and rendered via a dedicated Ink component (LY4 in minified code)
  3. Other tokens go through MX(), a custom token-to-ANSI renderer
  4. Links are wrapped in OSC 8 via _Z6(): \x1B]8;;URL\x07display_text\x1B]8;;\x07

The link renderer works correctly

The "link" case in MX() correctly produces OSC 8 hyperlinks:

case "link": {
  let text = (token.tokens ?? []).map(t => MX(t, ...)).join("");
  let plainText = stripAnsi(text);
  if (plainText && plainText !== token.href)
    return makeHyperlink(token.href, text);  // [text](url) → OSC 8 with display text
  return makeHyperlink(token.href);           // bare URL
}

The bug is in table width calculation

In the LY4 table component, column widths are calculated by:

  1. Rendering cell content via MX() (which produces OSC 8 sequences for links)
  2. Stripping ANSI with q_() (strip-ansi) to get plain text
  3. Measuring width with f1() (string-width)

The problem: The strip-ansi function (q_) uses a regex (QA9) designed for standard CSI sequences (\x1B[...m), but OSC 8 sequences (\x1B]8;;...\x07) are not fully handled during width calculation. This causes:

  • Column widths to be miscalculated (the URL inside the OSC 8 sequence inflates the perceived width)
  • The table to exceed terminal width
  • Fallback to card/list layout where link formatting is lost entirely

Additionally, the word-wrap function (E36, which is Bun.wrapAnsi or a JS fallback) may split OSC 8 escape sequences across lines when wrapping cell content, breaking the hyperlink.

Where in the code

In minified cli.js (v2.1.81):

  • _Z6 — creates OSC 8 hyperlinks (TY4 = "\x1B]8;;", kY4 = "\x07")
  • MX — token-to-ANSI renderer (handles all markdown token types)
  • LY4 — Ink table component with width calculation
  • q_ — strip-ansi function (uses regex QA9)
  • f1 — string-width function
  • yQ6E36 — word-wrap function

Suggested fix

In the table component's width-calculation helpers, ensure OSC 8 sequences are stripped before measuring:

// The strip function should also remove OSC 8 hyperlink sequences
// before measuring column widths:
function stripForWidth(text) {
  return text
    .replace(/\x1B]8;;[^\x07]*\x07([^\x1B]*)\x1B]8;;\x07/g, '$1')  // keep display text only
    .replace(ansiRegex, '');  // then strip remaining ANSI
}

This ensures [short text](very-long-url) is measured as the width of "short text", not the width of the entire escape sequence.

Environment

  • Claude Code version: 2.1.81
  • Terminal: iTerm2 / macOS (supports OSC 8)
  • OS: macOS

View original on GitHub ↗

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