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.

Reference

https://github.com/hpjansson/chafa/pull/324

Edited Jan 24, 2026 by yonghe zou
Assignee Loading
Time tracking Loading