Nested markdown lists: first item of a 3rd-level list is indented 2 spaces too deep

Status Fixed / completed
Reported on v2.1.220
Maintainer reply ✓ Yes — bcherny
Activity 3 comments · opened Jul 31, 2026 · closed Aug 20, 2026
💡 Likely answer: A maintainer (bcherny, collaborator) responded on this thread — see the highlighted reply below.

Environment

  • Claude Code 2.1.220 (native install)
  • macOS (Darwin 25.5.0), node v20.19.6

What happens

When assistant output contains a markdown list nested 3 levels deep, the first item of
the deepest list is rendered 2 spaces deeper than its siblings.

Source (assistant message text):

  • L1
  • L2
  • item A
  • item B

Rendered in the terminal:

  • L1
  • L2
  • item A <- 6 spaces
  • item B <- 4 spaces

Expected: item A and item B share the same indentation.
Two-level lists render correctly.

Cause

In the markdown-to-terminal renderer, list_item stringifies each child token and
prefixes the parent indent to that whole string. A nested list is a multi-line string,
so the parent indent lands on its first line only:

case "list_item": return (e.tokens ?? []).map((child) => {
    const d = render(child, { listDepth: n + 1, ... });
    return `${"  ".repeat(n)}${d}`;   // applied to the entire multi-line string
  }).join("")

Two-level lists are unaffected because the parent's n is 0.

Repro (extracted logic, plain node)

const NL = "\n";
function render(tok, n = 0, parent = null) {
  switch (tok.type) {
    case "list": return tok.items.map((it) => render(it, n, tok)).join("");
    case "list_item": return (tok.tokens || []).map((child) => "  ".repeat(n) + render(child, n + 1, tok)).join("");
    case "text": return parent && parent.type === "list_item" ? `- ${tok.text}${NL}` : tok.text;
  }
}
const row = (t) => ({ type: "list_item", tokens: [{ type: "text", text: t }] });
const nest = (label, inner) => ({ type: "list_item", tokens: [{ type: "text", text: label }, inner] });
const three = { type: "list", items: [nest("L1", { type: "list", items: [nest("L2", { type: "list", items: [row("item A"), row("item B")] })] })] };
console.log(render(three));
// => item A gets 6 spaces, item B gets 4

Non-workarounds

Changing the source indent width (2 / 4 / 6 spaces, tabs), changing the bullet marker,
inserting blank lines between items, and reordering items all fail — the displayed
indent is generated from listDepth, not from the source whitespace.

View original on GitHub ↗

3 Comments

benthepsychologist · 2 months ago

Same root cause, different trigger — no nesting required. A single-level list item that's simply long enough to word-wrap at the terminal's current width hits this identically: no 3rd-level nesting needed, just an item wider than the terminal.

This matches your diagnosis exactly — the word-wrapped text becomes a multi-line string for that list item's content, and the indent-prefixing in list_item only lands on the first line, same mechanism as the nested-list case above, just produced by word-wrap instead of a child list token.

Worth noting this is likely bigger-impact than the title suggests: 3rd-level nesting is rare, but any list item exceeding terminal width is common — any findings list, status report, or checklist with one long item hits it. Reproduces identically across kitty (macOS) → tmux → Linux VM, iTerm2, and Mosh on iOS, ruling out a terminal-specific cause.

bcherny collaborator · 1 month ago

Fixed in 2.1.235 — changelog: "Fixed nested markdown list items misaligning at depth 3+ and added a hanging indent to wrapped list items in the terminal UI" (https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md). Closing — reply to reopen if it still happens on the latest version.

🤖 Generated with Claude Code

github-actions[bot] · 4 days ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.