{"record":{"id":"fb53c56010aa490b","repo":"kovidgoyal/kitty","slug":"einval","errorCode":"EINVAL","errorMessage":"Cannot send a directory","messagePattern":"Cannot send a directory","errorType":"error_code","errorClass":"TransmissionError","httpStatus":null,"severity":"error","filePath":"kitty/file_transmission.py","lineNumber":659,"sourceCode":"\n    def commit(self, send_os_error: Callable[[OSError, str, 'ActiveReceive', str], None]) -> None:\n        directories = sorted((df for df in self.files.values() if df.ftype is FileType.directory), key=lambda x: len(x.name), reverse=True)\n        for df in directories:\n            with suppress(OSError):\n                # we ignore failures to apply directory metadata as we have already sent an OK for the dir\n                df.apply_metadata()\n\n\nclass SourceFile:\n    def __init__(self, ftc: FileTransmissionCommand):\n        self.file_id = ftc.file_id\n        self.path = ftc.name\n        self.ttype = ftc.ttype\n        self.waiting_for_signature = True if self.ttype is TransmissionType.rsync else False\n        self.transmitted = False\n        self.stat = os.stat(self.path, follow_symlinks=False)\n        if stat.S_ISDIR(self.stat.st_mode):\n            raise TransmissionError(ErrorCode.EINVAL, msg='Cannot send a directory', file_id=self.file_id)\n        self.compressor: ZlibCompressor | IdentityCompressor = IdentityCompressor()\n        self.target = b''\n        self.open_file: io.BufferedReader | None = None\n        if stat.S_ISLNK(self.stat.st_mode):\n            self.target = os.readlink(self.path).encode('utf-8')\n        else:\n            self.open_file = open(self.path, 'rb')\n            if ftc.compression is Compression.zlib:\n                self.compressor = ZlibCompressor()\n        from kittens.transfer import rsync\n\n        self.differ = rsync.Differ() if self.waiting_for_signature else None\n        self.buf = bytearray()\n        self.write_pos = 0\n\n    def write(self, b: ReadableBuffer) -> None:\n        self.buf[self.write_pos : self.write_pos + len(b)] = b\n        self.write_pos += len(b)","sourceCodeStart":641,"sourceCodeEnd":677,"githubUrl":"https://github.com/kovidgoyal/kitty/blob/6d5d0c440603ad9bdf6dcd599f73f6dde21acb44/kitty/file_transmission.py#L641-L677","documentation":"SourceFile.__init__ stats the outgoing path; if it is a directory, transmission is refused with TransmissionError(EINVAL, 'Cannot send a directory'). The file-transfer send protocol handles individual files/symlinks, not recursive directory sends, at this level.","triggerScenarios":"Constructing a SourceFile (via the send command handler) whose ftc.name resolves to a directory — e.g. passing a dir path where a file path is expected.","commonSituations":"Shell glob failing to expand so the literal dir path is passed; scripts that forget isdir checks; users trying to 'kitten transfer' a folder.","solutions":["Check os.path.isdir before enqueueing and expand to individual files (os.walk/scandir).","Verify glob expansion in the calling script produced file paths."],"exampleFix":"# before\nsend(path)\n# after\nif os.path.isdir(path):\n    for root, _, files in os.walk(path):\n        for f in files:\n            send(os.path.join(root, f))\nelse:\n    send(path)","handlingStrategy":"type-guard","validationCode":"import os\nassert not os.path.isdir(path), f'cannot send directory: {path}'","typeGuard":"def is_sendable_file(path: str) -> bool:\n    import os\n    return not os.path.isdir(path)","tryCatchPattern":"try:\n    SourceFile(ftc)\nexcept TransmissionError as e:\n    if e.code is ErrorCode.EINVAL and 'directory' in e.msg: expand_and_retry(path)\n    else: raise","preventionTips":["Expand directories to file lists before enqueueing.","Check glob expansion results in scripts."],"tags":["kitty","file-transfer","eisdir","directory"],"backgroundTag":"send-directory-unsupported","analyzedSha":"6d5d0c440603ad9bdf6dcd599f73f6dde21acb44","analyzedAt":"2026-08-27T14:20:20.142Z","schemaVersion":2},"datasetVersion":"2026-08-27T19:17:21.184Z"}