{"record":{"id":"b36bc889464fa3ed","repo":"kovidgoyal/kitty","slug":"json-payload-of-remote-command-is-invalid-must-be","errorCode":null,"errorMessage":"JSON payload of remote command is invalid, must be an object with a version field","messagePattern":"JSON payload of remote command is invalid, must be an object with a version field","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kitty/remote_control.py","lineNumber":55,"sourceCode":"T = TypeVar('T')\nif TYPE_CHECKING:\n    from .window import Window\n\n\ndef encode_response_for_peer(response: Any) -> bytes:\n    return b'\\x1bP@kitty-cmd' + json.dumps(response).encode('utf-8') + b'\\x1b\\\\'\n\n\ndef parse_cmd(serialized_cmd: memoryview, encryption_key: EllipticCurveKey) -> dict[str, Any]:\n    # See https://github.com/python/cpython/issues/74379 for why we cant use\n    # memoryview directly :((\n    try:\n        pcmd = json.loads(bytes(serialized_cmd))\n    except Exception:\n        log_error('Failed to parse JSON payload of remote command, ignoring it')\n        return {}\n    if not isinstance(pcmd, dict) or 'version' not in pcmd:\n        log_error('JSON payload of remote command is invalid, must be an object with a version field')\n        return {}\n    pcmd.pop('password', None)\n    if 'encrypted' in pcmd:\n        if pcmd.get('enc_proto', '1') != RC_ENCRYPTION_PROTOCOL_VERSION:\n            log_error(f'Ignoring encrypted rc command with unsupported protocol: {pcmd.get(\"enc_proto\")}')\n            return {}\n        pubkey = pcmd.get('pubkey', '')\n        if not pubkey:\n            log_error('Ignoring encrypted rc command without a public key')\n        d = AES256GCMDecrypt(encryption_key.derive_secret(base64.b85decode(pubkey)), base64.b85decode(pcmd['iv']), base64.b85decode(pcmd['tag']))\n        data = d.add_data_to_be_decrypted(base64.b85decode(pcmd['encrypted']), True)\n        pcmd = json.loads(data)\n        if not isinstance(pcmd, dict) or 'version' not in pcmd:\n            return {}\n        delta = time_ns() - pcmd.pop('timestamp')\n        if abs(delta) > 5 * 60 * 1e9:\n            log_error(\n                f'Ignoring encrypted rc command with timestamp {delta / 1e9:.1f} seconds from now.'","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/kovidgoyal/kitty/blob/6d5d0c440603ad9bdf6dcd599f73f6dde21acb44/kitty/remote_control.py#L37-L73","documentation":"The remote-control JSON parsed successfully but is not an object containing a 'version' field, which every kitty RC command must have. The command is rejected before any handler runs.","triggerScenarios":"parse_cmd receives valid JSON that is a list, string, or an object lacking the 'version' key (e.g. hand-crafted socket message).","commonSituations":"Custom scripts writing directly to the kitty socket without using the kitten remote-control client format.","solutions":["Send commands via 'kitten @<command>' or kitty/cli.py helpers so the version field is included","If hand-rolling payloads, include \"version\": [major, minor] matching the RC API version","Check for version skew between client and server kitty installs"],"exampleFix":"# before\necho '{\"cmd\":\"send-text\"}' | nc -U ~/.local/share/kitty/rc-socket\n\n# after\nkitten @send-text --match-tab title:mytab 'hello'","handlingStrategy":"validation","validationCode":"payload = {'version': [0, 28], 'cmd': 'send-text', ...}\nassert isinstance(payload, dict) and 'version' in payload","typeGuard":"def is_rc_dict(x) -> bool:\n    return isinstance(x, dict) and isinstance(x.get('version'), list)","tryCatchPattern":null,"preventionTips":["Use kitten @ commands instead of raw socket writes","Always include the version field; check docs for the current RC API version"],"tags":["kitty","remote-control","protocol-validation"],"backgroundTag":"missing-required-field","analyzedSha":"6d5d0c440603ad9bdf6dcd599f73f6dde21acb44","analyzedAt":"2026-08-27T14:20:20.142Z","schemaVersion":2},"datasetVersion":"2026-08-27T19:17:21.184Z"}