Kitty graphics probe: iTerm2's i=less OK reply fails the response regex and is typed into the prompt as GOK

Status Open
Reported on v2.1.275
Maintainer reply None cached
Activity 2 comments · opened Sep 18, 2026

Symptom

Every new session in iTerm2 starts with GOK already typed in the prompt box:

> GOK█

Deterministic, not load-dependent: it happens on every startup, in every directory, with no tmux/screen and no SSH.

Environment

  • Claude Code 2.1.275
  • iTerm2 3.5.11, TERM_PROGRAM=iTerm.app, TERM=xterm-256color, COLORTERM=truecolor
  • macOS 26.0 (Darwin 25.6.0), arm64
  • No multiplexer, local terminal

Root cause

The three characters are the payload of iTerm2's answer to the inline-image capability probe, left unconsumed and then treated as typed input.

1. We send the kitty graphics query (id 31):

function T(e){ return x(e,y), b(`i=${e},s=1,v=1,a=q,t=d,f=24`, "AAAA") }
var S = 31;
function uEr(){ return { request: T(S), match: (e) => e.type === "kittyGraphics" && e.id === S } }

2. iTerm2 answers without the i= key. Captured off /dev/tty with termios in raw mode:

sent   '\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\'
reply  b'\x1b_GOK\x1b\\'

For contrast, the other four probes in the same run all answer in the expected shape:

'\x1b[?u'         -> b'\x1b[?0u'
'\x1b[>0q'        -> b'\x1bP>|iTerm2 3.5.11\x1b\\'
'\x1b]11;?\x1b\\' -> b'\x1b]11;rgb:0000/0000/0000\x1b\\'
'\x1b[?6n'        -> b'\x1b[?5;1;1R'

3. Our parser requires i=<digits>;, so the reply never becomes an event:

/\x1b_G(?:[^;]*,)?i=(\d+)[^;]*;(.*?)\x1b\\/s

No kittyGraphics event is produced, match never fires, the query stays queued, and the bytes fall through to the key decoder. That drops the \x1b_ introducer and the \x1b\ terminator and inserts the payload as text, giving G + OK.

The kitty spec does say the response echoes the id it was sent, so iTerm2 is the non-conforming side here and we are reporting it there too. A capability probe that mistypes into the user's prompt on a widely used terminal seems worth hardening regardless.

Reproduction

Open Claude Code in iTerm2 3.5.11 and look at the prompt line. To see the reply on its own:

import os, termios, tty, select
fd = os.open("/dev/tty", os.O_RDWR); saved = termios.tcgetattr(fd)
try:
    tty.setraw(fd)
    os.write(fd, b"\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\")
    print(os.read(fd, 256) if select.select([fd], [], [], 1)[0] else b"(no reply)")
finally:
    termios.tcsetattr(fd, termios.TCSADRAIN, saved); os.close(fd)

Expected

The probe reply is consumed or discarded. Nothing reaches the prompt buffer.

Suggested fixes

  1. Accept an i=-less response. Make the i= group optional in the response pattern, and treat a bare OK as answering the outstanding graphics query. Only one is ever in flight, so match's e.id === S check has no ambiguity to resolve.
  2. Never route an unrecognized APC/DCS/OSC reply to the key decoder. An escape sequence we asked for and then failed to parse should be dropped, not typed. That would also have contained #78855, #78693 and #91530, which reach the same end state by different paths.

Secondary impact

The only other route to inline-image support is a name check against ["kitty", "ghostty"] (or kitty(>=0.28.0)) on the XTVERSION name, and iTerm2 reports iTerm2 3.5.11. So unforced, iTerm2 is always classified as not supporting inline images even though it does, which the probe result above demonstrates. Fixing the parse fixes both symptoms.

Workaround

export CLAUDE_CODE_FORCE_TERMINAL_IMAGES=1

This has to be exported by the shell before launch. Putting it in settings.json under env does not work, because that block is injected into child processes only and never into the process that runs the probe. Possibly worth a docs note of its own.

Related

#93004, #91530, #78855, #78693 are the same failure mode (probe replies reaching the input path or the screen) from timing and echo causes rather than a parse rejection.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗