kovidgoyal/kitty · warning

Received incomplete data for clipboard

Error message

Received incomplete data for clipboard

What it means

An OSC 52 clipboard write ended with base64 data whose length is not a multiple of 4. For the legacy OSC 52 protocol kitty tolerates it (no error channel) and logs this; for OSC 5522 it instead raises an abort sent back to the client.

Source

Thrown at kitty/clipboard.py:345

        if not self.currently_writing_mime:
            self.mime_map[mime] = MimePos(self.tempfile.tell(), -1)
            self.currently_writing_mime = mime
        self.write_base64_data(data)

    def flush_base64_data(self) -> None:
        if self.currently_writing_mime:
            incomplete = self.decoder.needs_more_data()
            self.decoder.reset()
            start = self.mime_map[self.currently_writing_mime][0]
            self.mime_map[self.currently_writing_mime] = MimePos(start, self.tempfile.tell() - start)
            self.currently_writing_mime = ''
            if incomplete:
                # the data is not padded to a multiple of four bytes. This is
                # tolerated for the legacy OSC 52 protocol as it has no way to
                # report errors to the client.
                if self.protocol_type is ProtocolType.osc_5522:
                    raise self.abort('Incomplete base64 data, missing padding bytes')
                log_error('Received incomplete data for clipboard')

    def write_base64_data(self, b: bytes | memoryview) -> None:
        if not self.max_size_exceeded:
            try:
                decoded = self.decoder.decode(b)
            except ValueError as e:
                raise self.abort(f'Invalid base64 data: {e}') from e
            if decoded:
                self.tempfile.write(decoded)
                if self.max_size > 0 and self.tempfile.tell() > self.max_size:
                    log_error(f'Clipboard write request has more data than allowed by clipboard_max_size ({self.max_size} bytes), ignoring further data')
                    self.max_size_exceeded = True

    def data_for(self, mime: str = 'text/plain', offset: int = 0, size: int = -1) -> bytes:
        start, full_size = self.mime_map[mime]
        if size == -1:
            size = full_size
        return self.tempfile.read(start + offset, size)

View on GitHub (pinned to 6d5d0c4406)

Solutions

  1. Update/fix the client app to send properly padded base64
  2. If it's your code, ensure the full OSC 52 sequence is written atomically and padded with '='
  3. Chunk boundaries must be multiples of 4 bytes except the final chunk

Example fix

# before (client)
printf '\033]52;c;%s\a' "$b64"   # b64 unpadded/truncated
# after
printf '\033]52;c;%s\a' "$(base64 -w0 "$file")"
Defensive patterns

Strategy: validation

Validate before calling

import base64
def valid_b64(s: str) -> bool:
    try:
        base64.b64decode(s + '=' * (-len(s) % 4), validate=True)
        return True
    except Exception:
        return False

Prevention

When it happens

Trigger: A terminal application sends OSC 52 with truncated/unpadded base64; flush_base64_data detects leftover bytes via the base64 decoder.

Common situations: Buggy terminal client apps (older tmux copy-mode scripts, custom ssh wrappers) that clip the escape sequence at a buffer boundary, often with large clipboards.

Related errors


AI-assisted analysis of kovidgoyal/kitty@6d5d0c4406 (2026-08-27). Data as JSON: /api/errors/4ea49d96319b524e. Report an issue: GitHub.