Kitty graphics protocol: multipart direct transmission fails when an intermediate chunk contains base64 padding (=)
Bug report
- iTerm2 version: 3.6.6
- macOS version: 26.2
- Preferences plist: can provide on request
- Debug log: can provide on request
Summary
When receiving multipart kitty image data (m=1 / m=0) via direct transmission (t=d), iTerm2 concatenates the base64 payload from each chunk and base64-decodes once at the end.
This works when the chunks are slices of a single base64 stream (so padding only appears at the very end), but it fails if a sender base64-encodes each raw chunk independently. In that case, intermediate chunks may legitimately end with = padding, and after concatenation the combined base64 string contains padding in the middle, causing Data(base64Encoded:) to reject it. The image is then not decoded/displayed.
Steps to reproduce
Run this in iTerm2:
python3 - <<'PY'
import base64, sys
def cmd(params, payload):
return f"\x1b_G{params};{payload}\x1b\\"
# 1x1 RGBA red pixel (raw32, 4 bytes)
data = bytes([0xff, 0x00, 0x00, 0xff])
# Encode each raw chunk independently -> first chunk ends with '=' padding
chunk1 = base64.b64encode(data[:2]).decode() # /wA=
chunk2 = base64.b64encode(data[2:]).decode() # AP8=
# Transmit in two parts (identifier i=1)
sys.stdout.write(cmd("a=t,f=32,s=1,v=1,i=1,m=1", chunk1))
sys.stdout.write(cmd("a=t,f=32,i=1,m=0", chunk2))
# Try to display the image
sys.stdout.write(cmd("a=p,i=1,c=1,r=1", ""))
sys.stdout.flush()
PY
What happened
iTerm2 responds with an error for the final transmit chunk (e.g. “could not decode payload”), and the image is not displayed.
What should have happened
I’m not 100% sure which behavior is intended by the spec here, but either would be useful:
Compatibility: accept this kind of multipart stream by base64-decoding per chunk (or otherwise tolerating padding in non-final chunks) and appending the decoded bytes.
If padding in non-final chunks is considered invalid: reject it with a clearer diagnostic and/or document that for multipart direct transmission the base64 stream must not contain = until the final chunk (e.g. if the sender base64-encodes raw chunks independently, raw chunk sizes need to be multiples of 3, or the sender must stream base64 across chunks).
Context / workaround
Tools like chafa/pixelterm-c may send large images split into raw byte chunks and base64-encode each chunk. If the raw chunk size is not a multiple of 3, intermediate base64 padding can appear, triggering this failure.
Using raw chunk sizes that are multiples of 3 (e.g. 63/510 bytes) avoids intermediate padding and works in iTerm2.