{"record":{"id":"0e694a6cc1a30ede","repo":"zylon-ai/private-gpt","slug":"empty-zip","errorCode":"EMPTY_ZIP","errorMessage":"ZIP file cannot be empty","messagePattern":"ZIP file cannot be empty","errorType":"error_code","errorClass":"SkillDomainError","httpStatus":null,"severity":"error","filePath":"private_gpt/server/skills/skills_files.py","lineNumber":167,"sourceCode":"    filename = f\"{field_name.rstrip('[]')}{ext}\"\n    return filename, mime\n\n\ndef _is_zip(upload: UploadFile) -> bool:\n    filename = (upload.filename or \"\").lower()\n    content_type = (upload.content_type or \"\").lower()\n    return filename.endswith(\".zip\") or content_type in {\n        \"application/zip\",\n        \"application/x-zip-compressed\",\n    }\n\n\ndef _extract_zip(upload: UploadFile, payload: bytes) -> list[tuple[str, bytes]]:\n    try:\n        with zipfile.ZipFile(io.BytesIO(payload)) as archive:\n            members = [item for item in archive.infolist() if not item.is_dir()]\n            if not members or all(item.file_size == 0 for item in members):\n                raise SkillDomainError(\n                    SkillErrorCode.EMPTY_ZIP, \"ZIP file cannot be empty\"\n                )\n\n            raw_entries = [\n                (item.filename, archive.read(item.filename)) for item in members\n            ]\n            return _flatten_wrapper_directory(raw_entries)\n    except SkillDomainError as e:\n        raise e\n    except Exception as exc:\n        raise SkillDomainError(\n            SkillErrorCode.INVALID_ZIP,\n            f\"Invalid ZIP file: {upload.filename or 'unnamed.zip'}\",\n        ) from exc\n\n\ndef _flatten_wrapper_directory(\n    entries: list[tuple[str, bytes]],","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/private_gpt/server/skills/skills_files.py#L149-L185","documentation":"SkillDomainError with code EMPTY_ZIP: _extract_zip raises this when the opened archive has no non-directory members, or all members have file_size == 0. It distinguishes 'structurally valid but contentless' archives from corrupt ones (INVALID_ZIP), so the user gets an actionable message.","triggerScenarios":"Uploading a zip that contains only directory entries; a zip whose every file member is zero bytes (e.g. created by `zip -r out.zip emptydir` or by a failed build step that touched empty files); a placeholder zip committed to make a form valid.","commonSituations":"CI artifacts produced before compilation/copy steps ran; zipping an empty staging directory when upstream download failed silently; `touch`-based stubs in test data; disk-full during archive creation truncating contents to headers only.","solutions":["Inspect the archive (`unzip -l out.zip`) to confirm it has non-empty file members.","Re-run the packaging step after verifying the source directory actually contains the built files (SKILL.md etc.).","Add a pre-upload check that the zip has at least one member with size > 0.","If a download produced the zip, verify its size/checksum before packaging."],"exampleFix":"# before\nimport zipfile\nzf = zipfile.ZipFile('out.zip', 'w')\nzf.close()  # empty archive -> EMPTY_ZIP on upload\n# after\nzf = zipfile.ZipFile('out.zip', 'w')\nzf.write('SKILL.md')\nzf.close()","handlingStrategy":"validation","validationCode":"import zipfile\n\ndef zip_has_content(path: str) -> bool:\n    with zipfile.ZipFile(path) as zf:\n        return any(not i.is_dir() and i.file_size > 0 for i in zf.infolist())","typeGuard":"const zipHasContent = (infos: { name: string; dir: boolean; size: number }[]): boolean =>\n  infos.some((i) => !i.dir && i.size > 0);","tryCatchPattern":null,"preventionTips":["Assert a non-empty member list after any packaging step in CI.","Verify source files exist and are non-empty before zipping.","Check downloaded artifacts' sizes before repackaging."],"tags":["zip","upload","skills","validation","domain-error"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}