jamiepine/voicebox · error · ValueError
Invalid ZIP file
Error message
Invalid ZIP file
What it means
Raised by the profile import path when Python's zipfile module emits zipfile.BadZipFile while opening the uploaded bytes (export_import.py:235). The bytes are not a valid ZIP container — truncated, wrong format, or zero-length. The handler normalizes the low-level BadZipFile into a ValueError("Invalid ZIP file") so callers see a single error type.
Source
Thrown at backend/services/export_import.py:236
tmp.write(zip_file.read(zip_path))
tmp_path = tmp.name
try:
# Add sample to profile
await add_profile_sample(
profile.id,
tmp_path,
reference_text,
db,
)
finally:
# Clean up temp file
Path(tmp_path).unlink(missing_ok=True)
return profile
except zipfile.BadZipFile:
raise ValueError("Invalid ZIP file")
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON in archive: {e}")
except Exception as e:
if isinstance(e, ValueError):
raise
raise ValueError(f"Error importing profile: {str(e)}")
def export_generation_to_zip(generation_id: str, db: Session) -> bytes:
"""
Export a generation to a ZIP archive.
Args:
generation_id: Generation ID to export
db: Database session
Returns:
ZIP file contents as bytesView on GitHub (pinned to 51f49dea19)
Solutions
- Verify the file magic bytes locally before upload: the first four bytes of a ZIP are b'PK\x03\x04'.
- Ensure the client sends the exact bytes produced by export_profile_to_zip without re-encoding (no base64, no form-wrapping unless the endpoint expects it).
- Re-export the profile on the source instance and retry; if it still fails, the export itself is the bug.
- Confirm you are hitting the profile import endpoint, not the generation import endpoint — they expect different manifests.
Example fix
// before (js client): sending a tarball
const body = new FormData(); body.append('file', tarballFile);
// after: validate magic bytes before sending
const buf = new Uint8Array(await file.arrayBuffer());
const isZip = buf[0]===0x50 && buf[1]===0x4b && buf[2]===0x03 && buf[3]===0x04;
if (!isZip) throw new Error('not a zip');
body.append('file', file); Defensive patterns
Strategy: validation
Validate before calling
import zipfile
def is_valid_zip(file_bytes: bytes) -> bool:
if len(file_bytes) < 4 or file_bytes[:4] != b'PK\x03\x04':
return False
try:
with zipfile.ZipFile(io.BytesIO(file_bytes), 'r') as z:
return z.testzip() is None
except zipfile.BadZipFile:
return False
# before calling import_profile_from_bytes:
if not is_valid_zip(file_bytes):
raise HTTPException(400, 'Please upload a valid .zip produced by the exporter.') Type guard
null
Try / catch
try:
profile = await import_profile_from_bytes(file_bytes, db)
except ValueError as e:
if str(e) == 'Invalid ZIP file':
raise HTTPException(400, 'The uploaded file is not a valid ZIP archive.')
raise HTTPException(400, str(e)) Prevention
- Validate the PK\x03\x04 magic bytes on the client before uploading.
- Always source import files from export_profile_to_zip on a matching version.
- Set the form input accept='.zip' so the OS file picker filters out non-zips.
When it happens
Trigger: Call to the profile import function with file_bytes that fails zipfile.ZipFile(BytesIO(file_bytes), 'r'). Happens when a user uploads a .tar/.gz, a raw WAV, an HTML error page from a failed download, or a file that was cut off mid-transfer.
Common situations: Browser sent the wrong file due to a renamed extension; a proxy/CDN returned an HTML error page with a .zip content-type; the file was chunked and the last chunk was dropped; the user is uploading a generations ZIP into the profile-import endpoint.
Related errors
- Unsupported audio format '{suffix}'. Allowed: {sorted(IMPORT
- Empty audio file.
- File too large. Maximum size is {MAX_FILE_SIZE / (1024 * 102
- {e}
- ZIP archive missing manifest.json
AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12).
Data as JSON: /api/errors/7c25028d0a2c8a19.
Report an issue: GitHub.