{"record":{"id":"ec56c0d31014819c","repo":"jamiepine/voicebox","slug":"profile-generation-profile-id-not-found","errorCode":null,"errorMessage":"Profile {generation.profile_id} not found","messagePattern":"Profile (.+?) not found","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"backend/services/export_import.py","lineNumber":267,"sourceCode":"    Args:\n        generation_id: Generation ID to export\n        db: Database session\n        \n    Returns:\n        ZIP file contents as bytes\n        \n    Raises:\n        ValueError: If generation not found\n    \"\"\"\n    # Get generation\n    generation = db.query(DBGeneration).filter_by(id=generation_id).first()\n    if not generation:\n        raise ValueError(f\"Generation {generation_id} not found\")\n    \n    # Get profile info\n    profile = db.query(DBVoiceProfile).filter_by(id=generation.profile_id).first()\n    if not profile:\n        raise ValueError(f\"Profile {generation.profile_id} not found\")\n    \n    # Get all versions for this generation\n    versions = (\n        db.query(DBGenerationVersion)\n        .filter_by(generation_id=generation_id)\n        .order_by(DBGenerationVersion.created_at)\n        .all()\n    )\n\n    # Create ZIP in memory\n    zip_buffer = io.BytesIO()\n    \n    with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:\n        # Build version manifest entries\n        version_entries = []\n        for v in versions:\n            v_path = config.resolve_storage_path(v.audio_path)\n            effects_chain = None","sourceCodeStart":249,"sourceCodeEnd":285,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/services/export_import.py#L249-L285","documentation":"Raised by export_generation_to_zip after the generation row is found but its profile_id does not resolve to a VoiceProfile (export_import.py:266-267). The generation references a profile that no longer exists — a dangling foreign key. This indicates the profile was deleted without cascading to (or before) its generations.","triggerScenarios":"Profile was hard-deleted while its generation rows remained; manual SQL deleted from voice_profiles without updating generations; a foreign-key constraint was disabled or absent at the schema level; the generation was imported and assigned to a profile_id that was later removed.","commonSituations":"Manual DB cleanup script that deleted profiles but not their generations; cascading FK not configured in the ORM model; orphaned rows after a partial migration.","solutions":["Restore the missing profile from backup, or re-assign the generation to an existing profile: UPDATE generations SET profile_id='<valid>' WHERE id='<id>';.","Audit the schema: ensure voice_profiles.id has ON DELETE CASCADE or restrict, and that the ORM relationship matches.","If the profile is genuinely gone, delete the orphaned generation too, then re-export will not be needed.","Add a DB-level foreign key constraint if one is missing so this cannot recur."],"exampleFix":"-- before: orphaned generation\nSELECT id FROM generations WHERE profile_id NOT IN (SELECT id FROM voice_profiles);\n-- after: reassign to an existing profile, then add the constraint\nUPDATE generations SET profile_id='<existing>' WHERE id='<orphan>';","handlingStrategy":"validation","validationCode":"def export_generation_safe(generation_id: str, db: Session) -> bytes:\n    gen = db.query(DBGeneration).filter_by(id=generation_id).first()\n    if not gen:\n        raise HTTPException(404, 'Generation not found')\n    if not db.query(DBVoiceProfile).filter_by(id=gen.profile_id).first():\n        raise HTTPException(409, 'Generation references a deleted profile; repair the DB before exporting.')\n    return export_generation_to_zip(generation_id, db)","typeGuard":"null","tryCatchPattern":"try:\n    zip_bytes = export_generation_to_zip(generation_id, db)\nexcept ValueError as e:\n    msg = str(e)\n    if 'not found' in msg and 'Profile' in msg:\n        raise HTTPException(409, msg)\n    raise HTTPException(400, msg)","preventionTips":["Add an ON DELETE CASCADE (or RESTRICT) FK from generations.profile_id to voice_profiles.id.","Run a periodic orphan check: SELECT id FROM generations WHERE profile_id NOT IN (SELECT id FROM voice_profiles).","Never delete profiles via raw SQL without handling their generations."],"tags":["export","foreign-key","data-integrity","generation"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}