{"record":{"id":"4c579be0db68116e","repo":"jamiepine/voicebox","slug":"zip-archive-missing-samples-json","errorCode":null,"errorMessage":"ZIP archive missing samples.json","messagePattern":"ZIP archive missing samples\\.json","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"backend/services/export_import.py","lineNumber":148,"sourceCode":"        \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            \n            if not isinstance(samples_data, dict):\n                raise ValueError(\"Invalid samples.json: must be a dictionary\")\n            ","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/services/export_import.py#L130-L166","documentation":"Same validation pass as error 233: samples.json (the filename-to-reference-text mapping) must be present at the ZIP root. Without it the importer can't associate each audio file with its reference text, so it rejects before parsing.","triggerScenarios":"A manifest-only archive, a partially-assembled export, or a hand-edited ZIP from which samples.json was stripped.","commonSituations":"Editing an exported ZIP and dropping samples.json; third-party archive lacking the mapping; truncated upload.","solutions":["Re-export the profile to obtain a complete archive.","If rebuilding manually, include a samples.json object mapping each wav filename to its reference text.","Don't strip entries from exported ZIPs."],"exampleFix":"# before — samples.json removed from the zip\nawait import_profile_from_zip(stripped_zip, db)\n\n# after — include samples.json at the zip root:\n# { \"abc.wav\": \"hello world\", \"def.wav\": \"second sample\" }","handlingStrategy":"validation","validationCode":"import io, zipfile\n\ndef has_samples_json(file_bytes) -> bool:\n    with zipfile.ZipFile(io.BytesIO(file_bytes)) as z:\n        return \"samples.json\" in z.namelist()","typeGuard":null,"tryCatchPattern":"try:\n    await import_profile_from_zip(file_bytes, db)\nexcept ValueError as e:\n    if \"missing samples.json\" in str(e):\n        # reject, ask for a complete archive\n        ...\n    raise","preventionTips":["Validate the full expected entry set (manifest.json + samples.json) before importing.","Warn users not to strip entries from exported archives by hand."],"tags":["profiles","import","zip","validation","manifest"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}