[BUG] Chat Markdown rendering mutates meaning of content (renumbers ordered lists)
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:
- 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 as3.,4.,5.. - Agent responses: When Claude selectively references prior claims (e.g., addressing only claims
1.,5.,6.,8.), the TUI renders1.,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
startattribute (which currently ships) does not solve discontinuous lists (3. 2. 1.still renders3. 4. 5.). Furthermore, usingN)as a workaround was eliminated in 2.1.246 whenN)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)
- Run
claude --bare - Submit this multi-line input:
```
- Three
- Two
- One
- Five
```
- Observed: Echoed message displays
3.,4.,5.,6. - Expected: Echoed message displays
3.,2.,1.,5.
Reproduction B (Agent Response)
- In an active session, submit:
```
Output the following lines verbatim:
- First
- Fifth
- Sixth
- Eighth
```
- Observed in TUI: Displays
1. First / 2. Fifth / 3. Sixth / 4. Eighth - 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)