kovidgoyal/kitty · error · StreamError

No stream_id in rc payload

Error message

No stream_id in rc payload

What it means

StreamError from kitty's remote-control streaming layer when a streamed data payload lacks a valid 'stream_id' string. stream_id correlates chunks with the in-flight stream started by a prior ESC @ stream-control sequence; without it the data cannot be routed and the command is rejected.

Source

Thrown at kitty/rc/base.py:440

                    windows += list(tab)
        return windows

    def create_async_responder(self, payload_get: PayloadGetType, window: Window | None) -> AsyncResponder:
        return AsyncResponder(payload_get, window)

    def message_to_kitty(self, global_opts: RCOptions, opts: Any, args: ArgsType) -> PayloadType:
        raise NotImplementedError()

    def response_from_kitty(self, boss: 'Boss', window: Optional['Window'], payload_get: PayloadGetType) -> ResponseType:
        raise NotImplementedError()

    def cancel_async_request(self, boss: 'Boss', window: Optional['Window'], payload_get: PayloadGetType) -> None:
        pass

    def handle_streamed_data(self, data: bytes, payload_get: PayloadGetType) -> BytesIO | AsyncResponse:
        stream_id = payload_get('stream_id')
        if not stream_id or not isinstance(stream_id, str):
            raise StreamError('No stream_id in rc payload')
        return self.stream_in_flight.handle_data(stream_id, data)


def cli_params_for(command: RemoteCommand) -> tuple[Callable[[], str], str, str, str]:
    return (command.options_spec or '\n').format, command.args.spec, command.desc, f'kitten @ {command.name}'


def parse_subcommand_cli(command: RemoteCommand, args: ArgsType) -> tuple[Any, ArgsType]:
    opts, items = parse_args(args[1:], *cli_params_for(command), result_class=command.options_class)
    if command.args.args_count is not None and command.args.args_count != len(items):
        if command.args.args_count == 0:
            raise SystemExit(f'Unknown extra argument(s) supplied to {command.name}')
        raise SystemExit(f'Must specify exactly {command.args.args_count} argument(s) for {command.name}')
    return opts, items


def display_subcommand_help(func: RemoteCommand) -> None:
    with suppress(SystemExit):

View on GitHub (pinned to 6d5d0c4406)

Solutions

  1. When starting a stream, capture the stream_id from the initial response and include it in every subsequent data payload
  2. Verify the payload key is exactly 'stream_id' and is a non-empty string
  3. Prefer using 'kitten @ ...' CLI or the kittens RPC helpers rather than hand-rolling the streaming protocol
Defensive patterns

Strategy: validation

Validate before calling

sid = payload.get('stream_id')
if not isinstance(sid, str) or not sid:
    raise ValueError('cannot stream without a stream_id from the initial stream-open response')
send_stream_chunk(sid, data)

Type guard

def has_stream_id(payload: dict) -> bool:
    sid = payload.get('stream_id')
    return isinstance(sid, str) and len(sid) > 0

Try / catch

try:
    conn.handle_streamed_data(data, payload_get)
except StreamError as e:
    if 'No stream_id' in str(e):
        reopen_stream()  # re-negotiate stream and resend with fresh id
    else:
        raise

Prevention

When it happens

Trigger: Calling handle_streamed_data with a payload whose stream_id is missing, empty, or not a string — e.g. hand-crafted escape sequences for remote control streaming that skip the stream_id field, or a client library that drops the key during JSON serialization.

Common situations: Writing a custom remote-control client, protocol-level scripts emitting kitty escape codes, or version mismatches where the client omits the newer stream_id field.

Related errors


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