{"record":{"id":"88b2164cccfc511f","repo":"jamiepine/voicebox","slug":"invalid-manifest-json-missing-generation-field","errorCode":null,"errorMessage":"Invalid manifest.json: missing generation.{field}","messagePattern":"Invalid manifest\\.json: missing generation\\.(.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"backend/services/export_import.py","lineNumber":379,"sourceCode":"                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\n            profile_id = None\n            profile_name = profile_data.get(\"name\", \"Unknown Profile\")\n            \n            # Try to find matching profile by name\n            if profile_name and profile_name != \"Unknown Profile\":\n                existing_profile = db.query(DBVoiceProfile).filter_by(name=profile_name).first()\n                if existing_profile:\n                    profile_id = existing_profile.id\n            ","sourceCodeStart":361,"sourceCodeEnd":397,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/services/export_import.py#L361-L397","documentation":"Raised during generation import when manifest['generation'] is present but is missing one of the required keys text, language, or duration (export_import.py:376-379, loop over required_fields). The {field} substitution names the first missing key. These three fields populate non-nullable columns on DBGeneration.","triggerScenarios":"A generation archive exported from an older build that did not record one of these fields (e.g. language was added later); a hand-edited manifest that stripped a field; an exporter on a fork that used different key names.","commonSituations":"Schema migration added language/duration as required after some exports were made; third-party archive generation; field renamed in a fork.","solutions":["Open manifest.json, locate generation.{field}, and add a valid value (text=str, language=ISO code like 'en', duration=float seconds).","Re-export from a source instance running the same schema version.","If the field genuinely does not apply, backfill a sensible default (e.g. language='en', duration=0.0) before importing.","Align your fork's exporter key names with required_fields."],"exampleFix":"// before: missing duration\n{\"generation\":{\"text\":\"hi\",\"language\":\"en\"}}\n// after\n{\"generation\":{\"text\":\"hi\",\"language\":\"en\",\"duration\":1.8}}","handlingStrategy":"validation","validationCode":"REQUIRED = {'text', 'language', 'duration'}\n\ndef generation_fields_present(file_bytes: bytes) -> list[str]:\n    \"\"\"Return the list of missing required generation fields (empty if ok).\"\"\"\n    with zipfile.ZipFile(io.BytesIO(file_bytes)) as z:\n        m = json.loads(z.read('manifest.json'))\n    gen = m.get('generation', {})\n    return [f for f in REQUIRED if f not in gen]","typeGuard":"def is_complete_generation(gen: object) -> bool:\n    return (\n        isinstance(gen, dict)\n        and isinstance(gen.get('text'), str) and gen['text']\n        and isinstance(gen.get('language'), str) and gen['language']\n        and isinstance(gen.get('duration'), (int, float))\n    )","tryCatchPattern":"try:\n    result = await import_generation_from_bytes(file_bytes, db)\nexcept ValueError as e:\n    if str(e).startswith('Invalid manifest.json: missing generation.'):\n        raise HTTPException(400, str(e))\n    raise HTTPException(400, str(e))","preventionTips":["Backfill missing manifest fields before importing rather than hand-editing rows after import.","Re-export from a same-version source when the schema has changed.","Validate the manifest dict against a Pydantic model before invoking the importer."],"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"}