Terminal images never draw under libghostty-based terminals (Herdr): kitty-graphics check rejects XTVERSION `libghostty`
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
herdrbinary 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
TMUXorSTYas 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
- Install Herdr 0.9.3 (or another app that embeds libghostty-vt) and run it inside Ghostty 1.3.1.
- Start
claudein a Herdr pane. Make sureCLAUDE_CODE_FORCE_TERMINAL_IMAGESis not set. - Load a mod that renders a terminal
<Image>and call$.ui.bliton it. - The alt text draws instead of the image, and blit is denied with the message above.
- Restart with
CLAUDE_CODE_FORCE_TERMINAL_IMAGES=1and 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.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗