{"record":{"id":"6dd8fb15d94f654f","repo":"jamiepine/voicebox","slug":"invalid-manifest-json-missing-generation-data","errorCode":null,"errorMessage":"Invalid manifest.json: missing generation data","messagePattern":"Invalid manifest\\.json: missing generation data","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"backend/services/export_import.py","lineNumber":370,"sourceCode":"    \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            # 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 \"generation\" not in manifest_data:\n                raise ValueError(\"Invalid manifest.json: missing generation data\")\n            \n            generation_data = manifest_data[\"generation\"]\n            profile_data = manifest_data.get(\"profile\", {})\n            \n            # Validate required fields\n            required_fields = [\"text\", \"language\", \"duration\"]\n            for field in required_fields:\n                if field not in generation_data:\n                    raise ValueError(f\"Invalid manifest.json: missing generation.{field}\")\n            \n            # Find audio file in archive\n            audio_files = [f for f in namelist if f.startswith(\"audio/\") and f.endswith(\".wav\")]\n            if not audio_files:\n                raise ValueError(\"No audio file found in ZIP archive\")\n            \n            audio_file_path = audio_files[0]\n            \n            # Check if we should match an existing profile or create metadata","sourceCodeStart":352,"sourceCodeEnd":388,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/services/export_import.py#L352-L388","documentation":"Raised during generation import when manifest.json has a 'version' but no 'generation' key (export_import.py:369-370). The 'generation' object carries text/language/duration — without it the importer has nothing to reconstruct. Most commonly hit when uploading a profile-export ZIP (which uses a 'profile' key) into the generation-import endpoint.","triggerScenarios":"Uploading a profile archive into the generation import flow (profile manifests key on 'profile', not 'generation'); a hand-built manifest that only included the profile block; an exporter bug that wrote version but omitted the generation block.","commonSituations":"Wrong endpoint for the file type; cross-flow confusion between profile and generation archives; schema drift between exporter and importer versions.","solutions":["Confirm you are using the generation import endpoint with a generation archive, not a profile archive.","Open manifest.json and ensure a top-level 'generation' object exists alongside 'version'.","Re-export the generation from a matching-version source instance.","If building the archive programmatically, copy the manifest shape from export_generation_to_zip exactly."],"exampleFix":"// before: profile-shaped manifest sent to generation import\n{\"version\":1, \"profile\":{\"name\":\"x\"}}\n// after: generation-shaped manifest\n{\"version\":1, \"generation\":{\"text\":\"...\",\"language\":\"en\",\"duration\":2.5}, \"profile\":{\"name\":\"x\"}}","handlingStrategy":"type-guard","validationCode":"import json, zipfile, io\n\ndef manifest_shape_ok(file_bytes: bytes) -> bool:\n    with zipfile.ZipFile(io.BytesIO(file_bytes)) as z:\n        m = json.loads(z.read('manifest.json'))\n    return isinstance(m, dict) and 'version' in m and isinstance(m.get('generation'), dict)","typeGuard":"def is_generation_manifest(obj: object) -> bool:\n    return (\n        isinstance(obj, dict)\n        and 'version' in obj\n        and isinstance(obj.get('generation'), dict)\n        and isinstance(obj.get('generation', {}).get('text'), str)\n    )","tryCatchPattern":"try:\n    result = await import_generation_from_bytes(file_bytes, db)\nexcept ValueError as e:\n    if 'missing generation data' in str(e):\n        raise HTTPException(400, 'This archive has no generation block — is it a profile export?')\n    raise HTTPException(400, str(e))","preventionTips":["Use the correct endpoint for each archive type (profile vs generation).","Programmatic archive builders should mirror export_generation_to_zip's manifest keys.","Cross-check schema versions between source and target instances."],"tags":["manifest","import","validation","schema","generation"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}