[BUG] Chat Markdown rendering mutates meaning of content (renumbers ordered lists)

Status Closed — not planned
Reported on v2.1.234
Maintainer reply None cached
Activity 0 comments · opened Sep 23, 2026 · closed Sep 23, 2026

Preflight Checklist

  • [x] I have searched existing issues and this is a consolidated reopen/synthesis of closed/stale reports
  • #87790
  • #92855
  • [x] This is a single bug report focused on the unified conversational transcript renderer
  • [x] I am using the latest version of Claude Code

What's Wrong?

The TUI Markdown renderer normalises ordered lists across the conversational transcript, rewriting literal list markers (N. and N)) into auto-incrementing sequential integers (1. 2. 3. 4. or start, start+1, start+2).

This occurs bidirectionally in the terminal interface:

  1. User prompt echo (introduced in 2.1.234): When a user responds out of order to numbered questions (e.g., 3., 2., 1.), the echoed prompt renders as 3., 4., 5..
  2. Agent responses: When Claude selectively references prior claims (e.g., addressing only claims 1., 5., 6., 8.), the TUI renders 1., 2., 3., 4..

This is strictly a presentation-layer defect. Session transcripts (~/.claude/projects/.../*.jsonl) confirm that raw message.content retains the exact authored digits in both directions. However, it causes severe semantic corruption in the UI:

  • User answers visually map to wrong question numbers or numbers that were never asked.
  • Agent back-references are corrupted, leading to confusing exchanges where the user asks about a "point 2" that the agent never wrote, and the agent denies having written it.
Relationship to Prior Reports
  • Unification of #87790 and user-turn reports: #87790 tracked this defect in agent turns; subsequent user-prompt reports tracked it in prompt echoes after 2.1.234 enabled Markdown rendering on prompts. Both share the identical renderer and root cause.
  • Clarification on prior closed issues (#51697, #55753, #61463, #64288, #69768, #75199): Preserving the list start attribute (which currently ships) does not solve discontinuous lists (3. 2. 1. still renders 3. 4. 5.). Furthermore, using N) as a workaround was eliminated in 2.1.246 when N) lists were brought under the standard Markdown renderer.

---

Root Cause Analysis

Per the CommonMark specification (§5.3), ordered lists capture only the numeric value of the first item (the start attribute). Child list_item AST nodes discard their raw numeric prefixes, and the rendering layer (Ink / AST mapper) computes displayed prefixes dynamically as:

$$\text{itemNumber} = \text{start} + \text{index}$$

While this normalization is standard for static document authoring, conversational messages use numbers as referential identifiers rather than sequential layout counters.

---

Proposed Safe Fix (Zero Side Effects on Markdown Parsing)

To eliminate the risk of mutating other syntax (e.g., code blocks, inline code, bold text, blockquotes, tables), do not alter block tokenization rules or apply raw string regex pre-escaping (which risks leaking literal backslashes like 3\.).

The fix should be applied exclusively at the token metadata and Ink component mapping stage:

1. Retain Literal Prefix in AST Tokenization

When tokenizing a list_item, capture the literal leading digits directly from token.raw instead of discarding them:

// In the listitem tokenizer/visitor:
const match = token.raw.match(/^(\s*)(\d+)([.)])/);
if (match) {
  token.literalNumber = match[2];
  token.delimiter = match[3];
}
2. Render Literal Prefix in the Ink View Component

When rendering list items inside <Box flexDirection="row">:

// Current logic:
// const displayIndex = (list.start || 1) + index;

// Proposed safe logic:
const prefix = item.literalNumber 
  ? `${item.literalNumber}${item.delimiter || '.'} ` 
  : `${(list.start || 1) + index}. `;

return (
  <Box flexDirection="row" key={index}>
    <Text>{prefix}</Text>
    <Box flexDirection="column">{renderChildren(item)}</Box>
  </Box>
);

Why this guarantees zero regressions:

  • Standard Markdown rules (code blocks, tables, bold, italics, links) remain untouched.
  • Truly sequential lists (1. 2. 3.) render identically to how they render today.
  • The change only touches the text prefix displayed before each item node.

---

Steps to Reproduce

Reproducible in any clean terminal with --bare.

Reproduction A (User Turn Echo)
  1. Run claude --bare
  2. Submit this multi-line input:

```

  1. Three
  2. Two
  3. One
  4. Five

```

  1. Observed: Echoed message displays 3., 4., 5., 6.
  2. Expected: Echoed message displays 3., 2., 1., 5.
Reproduction B (Agent Response)
  1. In an active session, submit:

```
Output the following lines verbatim:

  1. First
  2. Fifth
  3. Sixth
  4. Eighth

```

  1. Observed in TUI: Displays 1. First / 2. Fifth / 3. Sixth / 4. Eighth
  2. Raw transcript JSON: Confirms literal 1. First\n5. Fifth\n6. Sixth\n8. Eighth

---

Environment

  • Claude Code Version: 2.1.280
  • OS: Linux, macOS, Windows
  • Interface: Claude Code CLI - Terminal TUI (Ink)

View original on GitHub ↗