[FEATURE] CLI: `code` is the only block-level token given neither a container nor a screen-reader form
What happens
In the terminal CLI, a fenced code block in an assistant message is emitted as bare text followed by a newline. Nothing separates it from the paragraphs around it — no border, no background, no gutter, no relative indent. The only thing that can distinguish a code block from a paragraph is per-token syntax colour.
So a fence with no language tag renders byte-identical to prose: it resolves to plaintext, and highlight.js hands that straight back.
"Tag your fences" is not a workaround either. The scope-to-colour map assigns wt.reset to variable, params, title, symbol, subst, section, attribute and bullet, so a ``` `bash `` block containing a path, some flags and a $VAR` can come out with no colour on any token.
The practical cost: Claude produced a block of text for me to relay elsewhere, I read straight past it as ordinary prose, and asked "what did you want me to send?" while it was on screen.
Repro
Send Claude Code: "Reply with exactly the following and nothing else:" followed by four lines of English prose inside an untagged ``` ` ``` fence. The reply is visually identical to a paragraph.
Mechanism
From the shipped bundle (2.1.220): an assistant message renders as a row of a 2-column marker box and a body column, and the body column is the markdown component. That component walks the marked token list and special-cases exactly two token types, lifting each into its own Ink component with a real layout box — table, and blockquote as <Box borderStyle="quote" borderTop={false} borderBottom={false} borderRight={false} borderDimColor paddingLeft={1}>. Every other token is converted to an ANSI string and concatenated into a single <Text>.
code is in that second group, and its branch is:
case"code":{
let u=e.lang??"",
d=u.match(/^[\w.+#-]+/)?.[0]??"",
p=s&&u&&s.supportsLanguage(u)?u
:s&&d&&s.supportsLanguage(d)?d
:"plaintext",
f=u&&!s?.supportsLanguage(u)?wt.dim(u)+oq:"";
if(!s)return f+e.text+oq;
return f+s.highlight(e.text,{language:p})+oq;
}
oq is a bare newline. f — the only structural marker the branch can emit — is a dim language label that appears only when the tag is one highlight.js does not recognise. A correctly tagged block therefore gets less structure than a misspelled one, and an untagged block gets none.
codespan (inline backticks) fares better: it is painted in the theme's permission colour.
Screen-reader mode
I checked every branch of that renderer for output conditional on the screen-reader flag. Exactly one is conditional: table, which returns a linearised form instead of the ASCII grid. code ignores the flag, so a fenced block is byte-identical with and without screen-reader mode, and carries no textual marker announcing that it is code.
table has thus been given dedicated treatment twice — a layout component and a screen-reader form. code has been given neither.
Proposal
Give code the treatment table and blockquote already have: lift it out of the string path into a component with a container. The minimum useful version is the existing quote border style — a dim left edge and one column of padding. A two-space indent, or emitting the dim language label unconditionally rather than only on an unrecognised tag, would also help. Separately, a short spoken label in screen-reader mode would close the accessibility half.
The requirement is that it renders for an untagged block, since that is the case with no colour to fall back on.
The boundaries are already known: marked hands the renderer a code token carrying the exact text, and the streaming path tracks an open fence across chunk boundaries and re-emits it on the next chunk.
Existing workaround, and why it isn't enough
/copy re-lexes the source markdown and lists every top-level fenced block, which solves extracting a block cleanly. It does nothing for noticing one, which is the actual failure — you have to already know a block is there to go looking for it.
Closest existing reports
- #77920 (open) — read in full; it is about the syntax colours being basic ANSI and unreadable on dark backgrounds.
- #21034, #22406 — by title, both about syntax highlighting being wrong or chaotic.
- #45841, #18738, #62804 — by title, the VS Code chat panel rather than the CLI.
Each concerns a block you can already locate. This is about not being able to see that there is a block.
Environment
Claude Code 2.1.220, Linux (NixOS), kitty 0.47.4, classic (inline) TUI mode, light theme, TERM=xterm-kitty, COLORTERM=truecolor. syntaxHighlightingDisabled is unset and CLAUDE_CODE_SYNTAX_HIGHLIGHT is unset — highlighting is on and working. This is not a misconfiguration.