claude-code - ✅(Solved) Fix Inline image rendering in the terminal UI (OSC 1337 / Kitty graphics / sixel) [1 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#54546Fetched 2026-04-30 06:42:40
View on GitHub
Comments
1
Participants
2
Timeline
5
Reactions
0
Author
Timeline (top)
labeled ×3commented ×1cross-referenced ×1

Claude Code currently has no path for rendering an image inline in the terminal chat. The model can read images (via the Read tool) as multimodal input, and the IDE/web clients can render images, but the terminal TUI cannot display a PNG/JPG inline as part of the conversation.

This filing documents the architectural reason (it is not a missing config flag — it is a real missing feature), enumerates every workaround I tried and why each fails, and proposes a path forward.

Root Cause

  • Read tool reads the image as model input only. The transcript shows the literal text Read image (XX KB) with no inline display.
  • Any escape-sequence trick from the model's text output is sanitized/discarded by the TUI renderer.
  • Any escape-sequence trick from a Bash subprocess is captured by Claude Code's stdout pipe and shown as text, not passed to the terminal.
  • Writing to /dev/tty from a Bash subprocess fails with device not configured (no controlling terminal).
  • Writing to the iTerm2 pty slave directly (e.g. > /dev/ttys000, found via ps ancestry) succeeds at the syscall level but the bytes are immediately overwritten by Claude Code's next repaint, and Claude Code's row-accounting becomes inconsistent because it has no knowledge of the image's height.

Fix Action

Fix / Workaround

This filing documents the architectural reason (it is not a missing config flag — it is a real missing feature), enumerates every workaround I tried and why each fails, and proposes a path forward.

Workarounds I tried, and why each failed

PR fix notes

PR #54551: Proposal: inline image rendering in the terminal UI

Description (problem / solution / changelog)

Summary

Adds examples/inline-image-rendering/README.md as a feature proposal for inline image rendering in the Claude Code (CC) TUI, complementing tracking issue #54546.

CC is the only first-party Claude client today that cannot render images inline in its conversation surface (claude.ai web, the VS Code extension, and the JetBrains plugin all do). Use cases observed in real CC sessions where the gap is felt: math rendering (KaTeX→PNG), data viz from Python tools, visual diffs from /design-review and /qa skills, PDF page previews, Mermaid/Graphviz diagrams.

What this PR adds

A single Markdown file at examples/inline-image-rendering/README.md containing a self-contained design record:

  • Channel-passthrough matrix — empirical results from probing every external text channel into CC 2.1.123 (Bash stdout, MCP text/image content, every hook surface, status line, slash commands, output styles, --print mode, direct /dev/tty writes, direct iTerm2 PTY writes). None of them pass OSC 1337 / Kitty / sixel through. Bun.stripANSI is bound and called on every input path.
  • Binary finding — the ansi-escapes library's OSC 1337 emitter is already bundled in the CC binary but unreferenced. The marked text renderer's image() discards the link and emits only the alt text. Wiring those up is the work.
  • Recommended in-CC design — capability detection (TERM_PROGRAM / TERM / TMUX), markdown renderer hook, layout integration via phantom-placeholder rows in v1 (proper Yoga node integration as P2 follow-up), fallbacks (Unicode block art, ASCII placeholder), settings (inlineImages.enabled / maxWidth / fallback), and a /inline-images slash command toggle.
  • Tool integration — proposed inline_display parameter on Read, plus a future DisplayImage tool or markdown-image convention so the model can emit images without simultaneously feeding them as vision input.
  • Reference implementations — two working community projects built and tested while researching this:
    • cc-img-proxy (https://github.com/xodn348/cc-img-proxy) — PTY proxy that wraps claude, intercepts stdout, detects assistant-emitted sentinels and replaces them with OSC 1337 bytes. 10/10 tests passing including a full PTY round-trip e2e. Approximates true inline.
    • cclatex-preview — iTerm2 Python AutoLaunch agent. Runs inside the iTerm2 process, watches a JSONL request file, opens a sibling pane and injects the image. Bypasses CC entirely; survives repaints unconditionally. Side-pane only, not inline.
  • 4-phase roadmap for in-CC implementation.

What this PR does NOT add

  • No source-code changes — this is a self-contained design record. Actual implementation lives inside the closed CC bundle and needs Anthropic engineering time.
  • No copy of reference-implementation code into this repo — the reference repos are linked.

Why a PR rather than just a comment on #54546

The investigation produced enough material (channel matrix, binary findings, two working tools, a recommended design with concrete API shapes) that it deserves a self-contained record under examples/. Issue threads are not the right surface for a 200-line design document.

Test plan

  • File renders correctly on github.com
  • All linked URLs resolve (issue #54546, cc-img-proxy repo)
  • No code changes — nothing to test in CI
  • Maintainer review of the proposed design

🤖 Generated with Claude Code

Changed files

  • examples/inline-image-rendering/README.md (added, +218/-0)

Code Example

[1] model output → [2] Claude Code TUI renderer → [3] PTY[4] iTerm2 / Kitty / etc.
RAW_BUFFERClick to expand / collapse

Inline image rendering in the terminal UI (OSC 1337 / Kitty graphics / sixel)

Summary

Claude Code currently has no path for rendering an image inline in the terminal chat. The model can read images (via the Read tool) as multimodal input, and the IDE/web clients can render images, but the terminal TUI cannot display a PNG/JPG inline as part of the conversation.

This filing documents the architectural reason (it is not a missing config flag — it is a real missing feature), enumerates every workaround I tried and why each fails, and proposes a path forward.

Concrete repro

  1. In iTerm2 (TERM_PROGRAM=iTerm.app), run claude and ask the assistant to display a PNG that exists on disk.
  2. Whatever the assistant tries — Read tool, writing OSC 1337 to stdout, writing to /dev/tty, writing to the iTerm2 pty slave directly — no image appears inline in the chat.

Concretely tested image: a 19-tile contact sheet of LaTeX renders at ~/code/ccLatex/.omx/samples/golden-render-v2/contact-sheet.png (from my own ccLatex project — Unicode math rendered as PNG that I wanted Claude Code to show me inline).

Expected behavior

When running in a terminal that supports an inline image protocol (iTerm2 OSC 1337, Kitty/Ghostty APC graphics, WezTerm, Konsole, or sixel-capable terminals), the assistant should be able to emit an image and have it render inline in the chat transcript, occupying multiple terminal rows correctly, surviving repaints/scrolling/resize.

In terminals without such support, fall back to a placeholder (Unicode block art preview, ASCII placeholder, or a clickable file path).

Actual behavior

  • Read tool reads the image as model input only. The transcript shows the literal text Read image (XX KB) with no inline display.
  • Any escape-sequence trick from the model's text output is sanitized/discarded by the TUI renderer.
  • Any escape-sequence trick from a Bash subprocess is captured by Claude Code's stdout pipe and shown as text, not passed to the terminal.
  • Writing to /dev/tty from a Bash subprocess fails with device not configured (no controlling terminal).
  • Writing to the iTerm2 pty slave directly (e.g. > /dev/ttys000, found via ps ancestry) succeeds at the syscall level but the bytes are immediately overwritten by Claude Code's next repaint, and Claude Code's row-accounting becomes inconsistent because it has no knowledge of the image's height.

Architectural analysis (why this is structural, not a one-line fix)

For a PNG to become pixels, bytes must traverse four layers:

[1] model output → [2] Claude Code TUI renderer → [3] PTY → [4] iTerm2 / Kitty / etc.
Layer transitionStatusWhy it blocks images today
1 → 2blockedThe TUI renderer parses model text as Markdown and emits its own ANSI styling. Raw OSC 1337 / APC graphics from the model are stripped or escaped — there is no "passthrough" channel.
2 → 3blockedBash tool stdout is captured by Claude Code, not forwarded to the PTY. Tool output is shown as text in the transcript, not as raw terminal bytes.
3 → 4open in principle, blocked in practiceThe host terminal (iTerm2, Kitty, etc.) supports the protocols natively. Bytes that reach the PTY render correctly. The blocker is that nothing the model controls reaches the PTY directly.
4already supportedNot the blocker.

Even a "perfect injection" into the PTY is undermined by Claude Code's repaint model:

  • Claude Code uses the alternate screen buffer with cursor positioning and region clears, repainting on every state change (token stream, key event, scroll, resize). Anything injected outside the renderer is overwritten on the next frame.
  • Inline images in iTerm2/Kitty occupy multiple terminal rows. The TUI's line accounting does not know the height of an injected image, so layout below the image breaks.
  • Scrollback fidelity: even if an image survives the initial render, scrolling away and back triggers re-rendering of the message from the model's text — which has no image in it — and the image does not return.

Workarounds I tried, and why each failed

ApproachResult
Read tool on PNGImage enters model context as multimodal input. Transcript shows Read image (XX KB). ❌
Model emitting OSC 1337 in response textSanitized/escaped by TUI markdown renderer. ❌
Bash subprocess writing OSC 1337 to stdoutCaptured by Claude Code, displayed as text. ❌
Bash subprocess writing to /dev/ttydevice not configured — subprocess has no controlling tty. ❌
Bash subprocess writing to iTerm2 pty slave (/dev/ttys000) found via ps ancestryBytes reach iTerm2 (exit=0), but TUI repaint immediately overwrites and row accounting breaks. ❌
AppleScript tell application "iTerm" to inject bytes into current sessionSame outcome as direct pty write — TUI repaint clobbers. ❌
Open in Preview / external viewer (open file.png)Works, but is not "inline in the chat". ⚠️
Different iTerm2 pane (open -a iTerm2 ... or imgcat in another pane)Works, but not in-flow with the conversation. ⚠️
VS Code / JetBrains Claude Code extensionHost UI can render Markdown images. ✅ (different client)
claude.ai web UIRenders Markdown images normally. ✅ (different client)

The conclusion: inline image display in the terminal client is an unimplemented feature, not a configuration toggle that exists but is off.

Proposed design

Capability detection

Detect inline-image-capable terminals at startup using the standard signals:

  • TERM_PROGRAM=iTerm.app (and recent versions) → OSC 1337
  • TERM=xterm-kitty or KITTY_WINDOW_ID set → Kitty/Ghostty APC graphics protocol
  • TERM_PROGRAM=WezTerm → OSC 1337 (WezTerm-compatible)
  • TERM_PROGRAM=ghostty / GHOSTTY_* → Kitty graphics
  • TERM_PROGRAM=Apple_Terminal → no inline image support, fall back
  • Inside tmux (TMUX set) → only enable if allow-passthrough is set; otherwise fall back

Rendering pipeline

  1. Markdown image syntax ![alt](path-or-url) in model output is recognized by the renderer.
  2. The renderer fetches/loads the image (local path, or URL with caching).
  3. For each capable terminal, encode using the appropriate protocol:
    • iTerm2: OSC 1337 File=inline=1;preserveAspectRatio=1;width=<cells>;height=<rows>
    • Kitty: APC Gf=<format>,a=T,...
    • sixel: DCS q ... ST for terminals advertising sixel
  4. Compute the image's row footprint from the encoded size and reserve those rows in the renderer's layout state, so repaints, scrolling, and resize correctly preserve the image.
  5. On unsupported terminals, fall back in priority order:
    • Unicode half-block art (4×8 pixels per cell, 256-color or truecolor)
    • ASCII placeholder with file path (current behavior, but with the path made copy-friendly)

Tool integration

  • Read on an image file should additionally surface the image inline in the transcript on capable terminals (in addition to feeding it as multimodal input). A flag like inline_display=true could control this.
  • A new tool, e.g. DisplayImage(path) or a Markdown convention, could let the model explicitly emit images without the side effect of feeding them as input.

Configuration

  • settings.json keys: inlineImages.enabled (default auto), inlineImages.maxWidth (cells), inlineImages.fallback (unicode-blocks | ascii-placeholder | none).
  • Per-session toggle: /inline-images on|off|auto.

Why this matters (use cases)

  • Plotting / data viz: returning a chart from a Python tool and viewing it in flow.
  • Diagram rendering: Mermaid/PlantUML/Graphviz output displayed inline.
  • Math rendering: KaTeX/MathJax-to-PNG (the ccLatex project — that's the original motivation for this filing).
  • Visual diffs: showing before/after screenshots from a UI test or design review.
  • Design review skills: skills like /design-review and /qa already capture screenshots; today the user has to switch to Preview or a browser to actually see them.
  • PDF page previews: read-pdf could show rendered pages inline rather than just text.

Every other Claude client (web, VS Code extension, JetBrains plugin) can render Markdown images. The terminal is the only client that can't, and it's the one where most agentic work happens.

Environment

  • Claude Code: CLI (latest)
  • macOS: 25.3.0 (darwin)
  • Terminal: iTerm2 3.6.10 (which fully supports OSC 1337 inline images)
  • Shell: zsh
  • Native imgcat works in iTerm2 outside of Claude Code; the same bytes injected from a Claude Code subprocess do not survive Claude Code's repaint cycle.

Suggested next steps

  1. Confirm the architectural read above (is there an existing passthrough channel I missed?).
  2. Decide on the protocol matrix to support (iTerm2 OSC 1337 first is highest leverage).
  3. Land capability detection + a DisplayImage tool / Markdown image rendering with row-aware layout.
  4. Document the fallback behavior for unsupported terminals.

Happy to provide test fixtures (the ccLatex contact sheets are 19 tiles across three themes — a good visual regression target).

extent analysis

TL;DR

To enable inline image rendering in the terminal UI, implement a rendering pipeline that detects capable terminals, encodes images using the appropriate protocol, and reserves rows in the renderer's layout state.

Guidance

  • Detect inline-image-capable terminals at startup using standard signals such as TERM_PROGRAM and TERM.
  • Implement a rendering pipeline that recognizes Markdown image syntax, fetches/loads the image, and encodes it using the appropriate protocol (e.g., OSC 1337 for iTerm2).
  • Reserve rows in the renderer's layout state to correctly preserve the image during repaints, scrolling, and resize.
  • Fall back to Unicode half-block art or ASCII placeholder with file path on unsupported terminals.

Example

No code snippet is provided as the issue requires a high-level design and implementation of the rendering pipeline.

Notes

The proposed design should consider the different protocols supported by various terminals, such as iTerm2, Kitty, and WezTerm, and ensure that the implementation is flexible and adaptable to different environments.

Recommendation

Apply the proposed design and implementation of the rendering pipeline to enable inline image rendering in the terminal UI, starting with support for iTerm2 OSC 1337 protocol.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

When running in a terminal that supports an inline image protocol (iTerm2 OSC 1337, Kitty/Ghostty APC graphics, WezTerm, Konsole, or sixel-capable terminals), the assistant should be able to emit an image and have it render inline in the chat transcript, occupying multiple terminal rows correctly, surviving repaints/scrolling/resize.

In terminals without such support, fall back to a placeholder (Unicode block art preview, ASCII placeholder, or a clickable file path).

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - ✅(Solved) Fix Inline image rendering in the terminal UI (OSC 1337 / Kitty graphics / sixel) [1 pull requests, 1 comments, 2 participants]