zylon-ai/private-gpt · error · SkillDomainError
INVALID_ZIP
INVALID_ZIP
Error message
Invalid ZIP file: {upload.filename or 'unnamed.zip'} What it means
SkillDomainError with code INVALID_ZIP: any non-SkillDomainError exception raised while opening the archive with zipfile.ZipFile or reading its members is wrapped into this error, naming the upload's filename (or 'unnamed.zip'). Typical underlying causes are BadZipFile, truncated payloads, or read failures on individual members.
Source
Thrown at private_gpt/server/skills/skills_files.py:178
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]],
) -> list[tuple[str, bytes]]:
"""Find SKILL.md and strip wrapper directories above it.
Per the Agent Skills spec, SKILL.md defines the skill root.
This finds SKILL.md, determines its parent directory, and keeps only
files at or below that level, stripping any wrapper directories above.
Examples:
- repo-name/SKILL.md -> SKILL.md
- repo-name/skill-dir/SKILL.md -> SKILL.md (with sibling dirs excluded)
"""View on GitHub (pinned to 4a030776a3)
Solutions
- Verify integrity locally: `unzip -t file.zip` (or `python -m zipfile -t file.zip`) — it must report 'No errors detected'.
- Confirm the actual format with `file file.zip`; re-export as a genuine ZIP if it is tar/gz/7z.
- Re-download/re-transfer the archive and compare sizes/checksums with the source.
- Remove password protection before upload.
Example fix
# before
# artifact.zip is actually a gzip stream renamed
# after
import gzip, zipfile
with gzip.open('artifact.zip', 'rb') as f:
payload = f.read()
with zipfile.ZipFile('fixed.zip', 'w') as zf:
zf.writestr('SKILL.md', payload.decode()) # upload fixed.zip Defensive patterns
Strategy: validation
Validate before calling
import zipfile
def is_valid_zip(path: str) -> bool:
try:
with zipfile.ZipFile(path) as zf:
return zf.testzip() is None
except zipfile.BadZipFile:
return False Type guard
const looksLikeZip = (bytes: Uint8Array): boolean => bytes.length >= 4 && bytes[0] === 0x50 && bytes[1] === 0x4b && (bytes[2] === 3 || bytes[2] === 5 || bytes[2] === 7);
Try / catch
try {
await uploadSkill(zipBytes);
} catch (e: any) {
if (e?.code === 'INVALID_ZIP') throw new Error('Archive failed integrity check — re-download or re-export');
throw e;
} Prevention
- Run `python -m zipfile -t file.zip` (or unzip -t) before every upload.
- Compare checksums/sizes when transferring archives between systems.
- Never rename files of other formats to .zip.
When it happens
Trigger: Uploading a file with a .zip extension or 'application/zip' content-type that is not a real zip (HTML error page saved as .zip, gzip/tar masquerading, text); a zip truncated in transit (client-side size cap, proxy cut); a corrupted or encrypted archive; archive.read() failing on a member with a bad CRC.
Common situations: Downloading artifacts from a URL that returned a login page instead of the zip; incomplete multipart uploads; double-compressed or wrong-format files renamed to .zip; CI caches serving corrupted archives; password-protected zips from shared drives.
Related errors
AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15).
Data as JSON: /api/errors/6cbd002e1f60dd6e.
Report an issue: GitHub.