{"record":{"id":"1a3f54d9009845a2","repo":"kovidgoyal/kitty","slug":"cannot-write-to-a-closed-file","errorCode":null,"errorMessage":"Cannot write to a closed file","messagePattern":"Cannot write to a closed file","errorType":"exception","errorClass":"TransmissionError","httpStatus":null,"severity":"error","filePath":"kitty/file_transmission.py","lineNumber":517,"sourceCode":"        if self.mtime != FileTransmissionCommand.mtime:\n            if is_symlink:\n                with suppress(NotImplementedError):\n                    os.utime(self.name, ns=(self.mtime, self.mtime), follow_symlinks=False)\n            else:\n                os.utime(self.name, ns=(self.mtime, self.mtime))\n\n    def unlink_existing_if_needed(self, force: bool = False) -> None:\n        if force or self.needs_unlink:\n            with suppress(FileNotFoundError):\n                os.unlink(self.name)\n            self.existing_stat = None\n            self.needs_unlink = False\n\n    def write_data(self, all_files: dict[str, 'DestFile'], data: bytes | memoryview, is_last: bool) -> None:\n        if self.ftype is FileType.directory:\n            raise TransmissionError(code=ErrorCode.EISDIR, file_id=self.file_id, msg='Cannot write data to a directory entry')\n        if self.closed:\n            raise TransmissionError(file_id=self.file_id, msg='Cannot write to a closed file')\n        if self.ftype in (FileType.symlink, FileType.link):\n            self.link_target += data\n            self.bytes_written += len(data)\n            if is_last:\n                lt = self.link_target.decode('utf-8', 'replace')\n                base = self.make_parent_dirs()\n                self.unlink_existing_if_needed(force=True)\n                if lt.startswith('fid:'):\n                    lt = all_files[lt[4:]].name\n                    if self.ftype is FileType.symlink:\n                        lt = os.path.relpath(lt, os.path.dirname(self.name))\n                elif lt.startswith('fid_abs:'):\n                    lt = all_files[lt[8:]].name\n                elif lt.startswith('path:'):\n                    lt = lt[5:]\n                    if not os.path.isabs(lt) and self.ftype is FileType.link:\n                        lt = os.path.join(base, lt)\n                    lt = lt.replace('/', os.sep)","sourceCodeStart":499,"sourceCodeEnd":535,"githubUrl":"https://github.com/kovidgoyal/kitty/blob/6d5d0c440603ad9bdf6dcd599f73f6dde21acb44/kitty/file_transmission.py#L499-L535","documentation":"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.","triggerScenarios":"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.","commonSituations":"Network/SSH retry logic resending the final chunk; a sender bug emitting end_data then more data; multiplexers duplicating escape sequences.","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."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"df = receiver.files.get(ftc.file_id)\nif df is None or df.closed:\n    return  # drop stale frame","typeGuard":null,"tryCatchPattern":"try:\n    receiver.add_data(ftc)\nexcept TransmissionError as e:\n    if 'closed' in (e.msg or ''): pass  # duplicate after end_data\n    else: raise","preventionTips":["Make senders strictly ordered: start -> data* -> end_data, exactly once.","Design retries at transfer granularity, not frame granularity."],"tags":["kitty","file-transfer","closed-file"],"backgroundTag":"write-after-close","analyzedSha":"6d5d0c440603ad9bdf6dcd599f73f6dde21acb44","analyzedAt":"2026-08-27T14:20:20.142Z","schemaVersion":2},"datasetVersion":"2026-08-27T19:17:21.184Z"}