kovidgoyal/kitty · error · TransmissionError
Cannot write to a closed file
Error message
Cannot write to a closed file
What it means
write_data raises this TransmissionError when data arrives for a DestFile that has already been closed (a prior end_data completed the transfer for that file_id). It guards against writing to a finalized entry.
Source
Thrown at kitty/file_transmission.py:517
if self.mtime != FileTransmissionCommand.mtime:
if is_symlink:
with suppress(NotImplementedError):
os.utime(self.name, ns=(self.mtime, self.mtime), follow_symlinks=False)
else:
os.utime(self.name, ns=(self.mtime, self.mtime))
def unlink_existing_if_needed(self, force: bool = False) -> None:
if force or self.needs_unlink:
with suppress(FileNotFoundError):
os.unlink(self.name)
self.existing_stat = None
self.needs_unlink = False
def write_data(self, all_files: dict[str, 'DestFile'], data: bytes | memoryview, is_last: bool) -> None:
if self.ftype is FileType.directory:
raise TransmissionError(code=ErrorCode.EISDIR, file_id=self.file_id, msg='Cannot write data to a directory entry')
if self.closed:
raise TransmissionError(file_id=self.file_id, msg='Cannot write to a closed file')
if self.ftype in (FileType.symlink, FileType.link):
self.link_target += data
self.bytes_written += len(data)
if is_last:
lt = self.link_target.decode('utf-8', 'replace')
base = self.make_parent_dirs()
self.unlink_existing_if_needed(force=True)
if lt.startswith('fid:'):
lt = all_files[lt[4:]].name
if self.ftype is FileType.symlink:
lt = os.path.relpath(lt, os.path.dirname(self.name))
elif lt.startswith('fid_abs:'):
lt = all_files[lt[8:]].name
elif lt.startswith('path:'):
lt = lt[5:]
if not os.path.isabs(lt) and self.ftype is FileType.link:
lt = os.path.join(base, lt)
lt = lt.replace('/', os.sep)View on GitHub (pinned to 6d5d0c4406)
Solutions
- Make the sender idempotent: never emit data after end_data for a file_id.
- Deduplicate/resequence frames if going through a transport that can duplicate.
- Catch TransmissionError on the receiving side and drop the stale file_id gracefully.
Defensive patterns
Strategy: try-catch
Validate before calling
df = receiver.files.get(ftc.file_id)
if df is None or df.closed:
return # drop stale frame Try / catch
try:
receiver.add_data(ftc)
except TransmissionError as e:
if 'closed' in (e.msg or ''): pass # duplicate after end_data
else: raise Prevention
- Make senders strictly ordered: start -> data* -> end_data, exactly once.
- Design retries at transfer granularity, not frame granularity.
When it happens
Trigger: Sending action=data for a file_id after an action=end_data was already delivered for it — duplicate frames, retries, or out-of-order delivery in handle_receive_cmd -> add_data.
Common situations: Network/SSH retry logic resending the final chunk; a sender bug emitting end_data then more data; multiplexers duplicating escape sequences.
Related errors
- Not an appropriate file type
- No valid action specified in file transmission command
- EISDIR
- Unknown link target type
- The file_id {ftc.file_id} already exists
AI-assisted analysis of kovidgoyal/kitty@6d5d0c4406 (2026-08-27).
Data as JSON: /api/errors/1a3f54d9009845a2.
Report an issue: GitHub.