Terminal images never draw under libghostty-based terminals (Herdr): kitty-graphics check rejects XTVERSION `libghostty`

Status Open
Reported on v2.1.296
Maintainer reply None cached
Activity 1 comment · opened Oct 11, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report
  • [x] I am using the latest version of Claude Code

What's Wrong?

A mod's terminal <Image> always draws its alt text when Claude Code runs inside a terminal built on libghostty-vt. In my case that's Herdr 0.9.3 running in Ghostty 1.3.1. $.ui.blit on the keyed Image is denied with:

the Image draws its alt here: the terminal draws no placeholder images (probe: graphics reply OK, terminal libghostty)

The graphics probe gets an OK reply, but the result is thrown away because of the terminal name.

In 2.1.296 the kitty-graphics decision is graphicsReplied && nameAllowed(xtversionName). The name check only accepts names in ["kitty", "ghostty"]:

var c = ["kitty", "ghostty"], m = "0.28.0";
function iWo(e) {
  if (e === null) return false;
  if (e.startsWith("kitty(")) { /* version check */ }
  return c.some((t) => e === t || e.startsWith(`${t} `));
}
// probe:
u.answer("kittyGraphics", J !== undefined && iWo(j),
  `probe: graphics reply ${J.message}, terminal ${j}`);

libghostty-vt answers XTVERSION with the bare string libghostty, so it fails e === "ghostty" and e.startsWith("ghostty "). Claude Code already recognizes libghostty as an XTVERSION name (it's in the set ENo() uses to normalize reported terminal names), but the image check doesn't accept it.

I sent CSI > 0 q (XTVERSION) plus a Kitty a=q query and recorded the raw replies:

Ghostty.app 1.3.1 window:  ESC P >|ghostty 1.3.1 ESC \   ESC _Gi=31;OK ESC \
Herdr 0.9.3 pane:          ESC P >|libghostty ESC \      ESC _Gi=31;OK ESC \
  • Plain Ghostty passes the check and is not affected.
  • The herdr binary embeds libghostty-vt (crates/ghostty-vt). It also implements Kitty graphics, including Ghostty's Unicode placeholder / virtual placement code, and re-places images on the outer terminal.
  • Claude Code only treats TMUX or STY as a multiplexer, so Herdr goes down the normal probe path, and the name check is the only thing that fails.

A related check, dWo(), uses xtversionName.toLowerCase().startsWith("ghostty") for hyperlink click handling. It misses libghostty the same way.

What Should Happen?

When the graphics probe replies OK and the terminal identifies as libghostty, images should draw.

Error Messages/Logs

the Image draws its alt here: the terminal draws no placeholder images (probe: graphics reply OK, terminal libghostty)

Steps to Reproduce

  1. Install Herdr 0.9.3 (or another app that embeds libghostty-vt) and run it inside Ghostty 1.3.1.
  2. Start claude in a Herdr pane. Make sure CLAUDE_CODE_FORCE_TERMINAL_IMAGES is not set.
  3. Load a mod that renders a terminal <Image> and call $.ui.blit on it.
  4. The alt text draws instead of the image, and blit is denied with the message above.
  5. Restart with CLAUDE_CODE_FORCE_TERMINAL_IMAGES=1 and the image draws.

Claude Model

Not model-specific

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.296 (Claude Code)

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Herdr 0.9.3 inside Ghostty 1.3.1, zsh (TERM_PROGRAM=ghostty, TERM=xterm-256color, not in tmux)

Additional Information

Workaround: CLAUDE_CODE_FORCE_TERMINAL_IMAGES=1.

Possible fix: add libghostty to the accepted names in iWo (and to the dWo prefix check).

One caveat. libghostty-vt answers the graphics query on its own, so an embedder that parses Kitty graphics but never draws them would still reply OK. If that's why the list is strict, a per-embedder XTVERSION name (Herdr could report herdr 0.9.3, like tmux does) might be the cleaner long-term fix. I can open that request on the Herdr side too.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗