kovidgoyal/kitty · error · TransmissionError
EINVAL
EINVAL
Error message
Cannot send a directory
What it means
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.
Source
Thrown at kitty/file_transmission.py:659
def commit(self, send_os_error: Callable[[OSError, str, 'ActiveReceive', str], None]) -> None:
directories = sorted((df for df in self.files.values() if df.ftype is FileType.directory), key=lambda x: len(x.name), reverse=True)
for df in directories:
with suppress(OSError):
# we ignore failures to apply directory metadata as we have already sent an OK for the dir
df.apply_metadata()
class SourceFile:
def __init__(self, ftc: FileTransmissionCommand):
self.file_id = ftc.file_id
self.path = ftc.name
self.ttype = ftc.ttype
self.waiting_for_signature = True if self.ttype is TransmissionType.rsync else False
self.transmitted = False
self.stat = os.stat(self.path, follow_symlinks=False)
if stat.S_ISDIR(self.stat.st_mode):
raise TransmissionError(ErrorCode.EINVAL, msg='Cannot send a directory', file_id=self.file_id)
self.compressor: ZlibCompressor | IdentityCompressor = IdentityCompressor()
self.target = b''
self.open_file: io.BufferedReader | None = None
if stat.S_ISLNK(self.stat.st_mode):
self.target = os.readlink(self.path).encode('utf-8')
else:
self.open_file = open(self.path, 'rb')
if ftc.compression is Compression.zlib:
self.compressor = ZlibCompressor()
from kittens.transfer import rsync
self.differ = rsync.Differ() if self.waiting_for_signature else None
self.buf = bytearray()
self.write_pos = 0
def write(self, b: ReadableBuffer) -> None:
self.buf[self.write_pos : self.write_pos + len(b)] = b
self.write_pos += len(b)View on GitHub (pinned to 6d5d0c4406)
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.
Example fix
# before
send(path)
# after
if os.path.isdir(path):
for root, _, files in os.walk(path):
for f in files:
send(os.path.join(root, f))
else:
send(path) Defensive patterns
Strategy: type-guard
Validate before calling
import os
assert not os.path.isdir(path), f'cannot send directory: {path}' Type guard
def is_sendable_file(path: str) -> bool:
import os
return not os.path.isdir(path) Try / catch
try:
SourceFile(ftc)
except TransmissionError as e:
if e.code is ErrorCode.EINVAL and 'directory' in e.msg: expand_and_retry(path)
else: raise Prevention
- Expand directories to file lists before enqueueing.
- Check glob expansion results in scripts.
When it happens
Trigger: 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.
Common situations: Shell glob failing to expand so the literal dir path is passed; scripts that forget isdir checks; users trying to 'kitten transfer' a folder.
Related errors
- EISDIR
- Not an appropriate file type
- No valid action specified in file transmission command
- Cannot write to a closed file
- Unknown link target type
AI-assisted analysis of kovidgoyal/kitty@6d5d0c4406 (2026-08-27).
Data as JSON: /api/errors/fb53c56010aa490b.
Report an issue: GitHub.