zylon-ai/private-gpt · error · SkillDomainError

EMPTY_ZIP

EMPTY_ZIP

Error message

ZIP file cannot be empty

What it means

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.

Source

Thrown at private_gpt/server/skills/skills_files.py:167

    filename = f"{field_name.rstrip('[]')}{ext}"
    return filename, mime


def _is_zip(upload: UploadFile) -> bool:
    filename = (upload.filename or "").lower()
    content_type = (upload.content_type or "").lower()
    return filename.endswith(".zip") or content_type in {
        "application/zip",
        "application/x-zip-compressed",
    }


def _extract_zip(upload: UploadFile, payload: bytes) -> list[tuple[str, bytes]]:
    try:
        with zipfile.ZipFile(io.BytesIO(payload)) as archive:
            members = [item for item in archive.infolist() if not item.is_dir()]
            if not members or all(item.file_size == 0 for item in members):
                raise SkillDomainError(
                    SkillErrorCode.EMPTY_ZIP, "ZIP file cannot be empty"
                )

            raw_entries = [
                (item.filename, archive.read(item.filename)) for item in members
            ]
            return _flatten_wrapper_directory(raw_entries)
    except SkillDomainError as e:
        raise e
    except Exception as exc:
        raise SkillDomainError(
            SkillErrorCode.INVALID_ZIP,
            f"Invalid ZIP file: {upload.filename or 'unnamed.zip'}",
        ) from exc


def _flatten_wrapper_directory(
    entries: list[tuple[str, bytes]],

View on GitHub (pinned to 4a030776a3)

Solutions

  1. Inspect the archive (`unzip -l out.zip`) to confirm it has non-empty file members.
  2. Re-run the packaging step after verifying the source directory actually contains the built files (SKILL.md etc.).
  3. Add a pre-upload check that the zip has at least one member with size > 0.
  4. If a download produced the zip, verify its size/checksum before packaging.

Example fix

# before
import zipfile
zf = zipfile.ZipFile('out.zip', 'w')
zf.close()  # empty archive -> EMPTY_ZIP on upload
# after
zf = zipfile.ZipFile('out.zip', 'w')
zf.write('SKILL.md')
zf.close()
Defensive patterns

Strategy: validation

Validate before calling

import zipfile

def zip_has_content(path: str) -> bool:
    with zipfile.ZipFile(path) as zf:
        return any(not i.is_dir() and i.file_size > 0 for i in zf.infolist())

Type guard

const zipHasContent = (infos: { name: string; dir: boolean; size: number }[]): boolean =>
  infos.some((i) => !i.dir && i.size > 0);

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15). Data as JSON: /api/errors/0e694a6cc1a30ede. Report an issue: GitHub.