{"record":{"id":"b66a483ac5e3376e","repo":"jamiepine/voicebox","slug":"source-version-not-found","errorCode":null,"errorMessage":"Source version not found","messagePattern":"Source version not found","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"error","filePath":"backend/routes/effects.py","lineNumber":186,"sourceCode":"        raise HTTPException(status_code=404, detail=\"Generation not found\")\n    if (gen.status or \"completed\") != \"completed\":\n        raise HTTPException(status_code=400, detail=\"Generation is not completed\")\n\n    from ..services import versions as versions_mod\n    from ..utils.effects import apply_effects, validate_effects_chain\n    from ..utils.audio import load_audio, save_audio\n\n    chain_dicts = [e.model_dump() for e in data.effects_chain]\n    error = validate_effects_chain(chain_dicts)\n    if error:\n        raise HTTPException(status_code=400, detail=error)\n\n    all_versions = versions_mod.list_versions(generation_id, db)\n    source_version_id = data.source_version_id\n    if source_version_id:\n        source_version = next((v for v in all_versions if v.id == source_version_id), None)\n        if not source_version:\n            raise HTTPException(status_code=404, detail=\"Source version not found\")\n        source_path = source_version.audio_path\n    else:\n        clean_version = next((v for v in all_versions if v.effects_chain is None), None)\n        if not clean_version:\n            source_path = gen.audio_path\n        else:\n            source_path = clean_version.audio_path\n            source_version_id = clean_version.id\n\n    resolved_source_path = config.resolve_storage_path(source_path)\n    if resolved_source_path is None or not resolved_source_path.exists():\n        raise HTTPException(status_code=404, detail=\"Source audio file not found\")\n\n    audio, sample_rate = await asyncio.to_thread(load_audio, str(resolved_source_path))\n    processed_audio = await asyncio.to_thread(apply_effects, audio, sample_rate, chain_dicts)\n\n    version_id = str(uuid.uuid4())\n    processed_path = config.get_generations_dir() / f\"{generation_id}_{version_id[:8]}.wav\"","sourceCodeStart":168,"sourceCodeEnd":204,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/routes/effects.py#L168-L204","documentation":"Returned as HTTP 404 by POST /generations/{generation_id}/versions/apply-effects. When the request supplies source_version_id, the route looks it up via next((v for v in list_versions(generation_id) ...) ...). If no version with that id belongs to this generation, it raises the 404. The version must belong to the same generation (cross-generation version ids are rejected).","triggerScenarios":"Passing a source_version_id that does not exist; passing a version id that belongs to a different generation; passing a version id that was deleted between page load and submit; sending a generation id where a version id is expected.","commonSituations":"UI dropdown of versions is stale after a delete/regenerate; client constructs the request with the wrong field; user picks a version from another generation in a mixed list; version id copy-paste error.","solutions":["Source source_version_id only from a fresh GET /generations/{generation_id}/versions response.","On 404, re-fetch the versions list and repopulate the picker.","Omit source_version_id entirely to let the backend pick the clean version (effects_chain is None) or fall back to gen.audio_path.","Verify the id belongs to the current generation before submitting."],"exampleFix":"// before\npost(url, { source_version_id: rememberedId, effects_chain })\n// after\nconst versions = await api.get(`/generations/${genId}/versions`);\nconst src = versions.find(v => v.id === rememberedId);\npost(url, { source_version_id: src ? src.id : undefined, effects_chain })","handlingStrategy":"validation","validationCode":"# Confirm source_version_id belongs to this generation first.\nversions = versions_mod.list_versions(generation_id, db)\nif data.source_version_id and not any(v.id == data.source_version_id for v in versions):\n    raise SourceVersionMissing(data.source_version_id)\n# safe to POST","typeGuard":"def source_version_in_generation(source_id: str, versions: list) -> bool:\n    return any(v.id == source_id for v in versions)","tryCatchPattern":"try:\n    client.post(url, json=payload)\nexcept HTTPStatusError as e:\n    if e.response.status_code == 404 and 'Source version' in e.response.json()['detail']:\n        versions = client.get(f'/generations/{gen_id}/versions').json()\n        payload['source_version_id'] = None  # let backend pick the clean version\n        client.post(url, json=payload)\n        return\n    raise","preventionTips":["Source source_version_id from a fresh versions listing for the same generation.","Omit source_version_id to let the backend choose the clean version automatically.","On 404, re-fetch versions and repopulate the picker.","Verify v.generation_id matches before submitting."],"tags":["fastapi","http-404","generations","versions","effects","not-found","rest"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}