{"record":{"id":"533ab904bb1a1c77","repo":"jamiepine/voicebox","slug":"zip-archive-missing-manifest-json","errorCode":null,"errorMessage":"ZIP archive missing manifest.json","messagePattern":"ZIP archive missing manifest\\.json","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"backend/services/export_import.py","lineNumber":145,"sourceCode":"    Args:\n        file_bytes: ZIP file contents\n        db: Database session\n        \n    Returns:\n        Created profile\n        \n    Raises:\n        ValueError: If ZIP is invalid or missing required files\n    \"\"\"\n    zip_buffer = io.BytesIO(file_bytes)\n    \n    try:\n        with zipfile.ZipFile(zip_buffer, 'r') as zip_file:\n            # Validate ZIP structure\n            namelist = zip_file.namelist()\n            \n            if \"manifest.json\" not in namelist:\n                raise ValueError(\"ZIP archive missing manifest.json\")\n            \n            if \"samples.json\" not in namelist:\n                raise ValueError(\"ZIP archive missing samples.json\")\n            \n            # Read manifest\n            manifest_data = json.loads(zip_file.read(\"manifest.json\"))\n            \n            if \"version\" not in manifest_data:\n                raise ValueError(\"Invalid manifest.json: missing version\")\n            \n            if \"profile\" not in manifest_data:\n                raise ValueError(\"Invalid manifest.json: missing profile\")\n            \n            profile_data = manifest_data[\"profile\"]\n            \n            # Read samples mapping\n            samples_data = json.loads(zip_file.read(\"samples.json\"))\n            ","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/services/export_import.py#L127-L163","documentation":"import_profile_from_zip() requires a manifest.json entry at the ZIP root. If zip_file.namelist() lacks it, the import is rejected before any parsing. manifest.json carries the version/profile metadata required to reconstruct the profile.","triggerScenarios":"Uploading a ZIP that wasn't produced by export_profile_to_zip — a random zip, a hand-rolled archive, or a generation-export ZIP (different manifest shape) aimed at the profile-import endpoint.","commonSituations":"User zips a folder of wavs instead of using Export; cross-endpoint confusion (generation import vs profile import); third-party archive.","solutions":["Only import ZIPs produced by the profile-export flow.","If migrating from another tool, write a converter that emits manifest.json + samples.json in the expected layout.","Confirm the file is a profile export, not a generation export."],"exampleFix":"# before — arbitrary zip of wavs\nawait import_profile_from_zip(arbitrary_zip_bytes, db)\n\n# after — produce a profile archive first\nexported = export_profile_to_zip(pid, src_db)\nawait import_profile_from_zip(exported, dst_db)","handlingStrategy":"validation","validationCode":"import io, zipfile\n\ndef has_manifest(file_bytes) -> bool:\n    with zipfile.ZipFile(io.BytesIO(file_bytes)) as z:\n        return \"manifest.json\" in z.namelist()","typeGuard":null,"tryCatchPattern":"try:\n    await import_profile_from_zip(file_bytes, db)\nexcept ValueError as e:\n    if \"missing manifest.json\" in str(e):\n        # reject upload with a clear 'use Export to produce the archive' message\n        ...\n    raise","preventionTips":["Validate the ZIP structure client-side before upload.","Document the required archive layout for profile imports."],"tags":["profiles","import","zip","validation","manifest"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}