{"record":{"id":"e22a6ff8944c655b","repo":"jamiepine/voicebox","slug":"empty-audio-file","errorCode":null,"errorMessage":"Empty audio file.","messagePattern":"Empty audio file\\.","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"backend/routes/generations.py","lineNumber":449,"sourceCode":"            detail=f\"Unsupported audio format '{suffix}'. Allowed: {sorted(IMPORT_AUDIO_EXTENSIONS)}\",\n        )\n\n    chunks: list[bytes] = []\n    total = 0\n    while True:\n        chunk = await file.read(1024 * 1024)\n        if not chunk:\n            break\n        total += len(chunk)\n        if total > IMPORT_AUDIO_MAX_BYTES:\n            raise HTTPException(\n                status_code=413,\n                detail=f\"File exceeds {IMPORT_AUDIO_MAX_BYTES // (1024 * 1024)} MB limit.\",\n            )\n        chunks.append(chunk)\n    audio_bytes = b\"\".join(chunks)\n    if not audio_bytes:\n        raise HTTPException(status_code=400, detail=\"Empty audio file.\")\n\n    generation_id = str(uuid.uuid4())\n    target = config.get_generations_dir() / f\"{generation_id}{suffix}\"\n    target.write_bytes(audio_bytes)\n\n    try:\n        audio, sr = load_audio(str(target))\n        duration = float(len(audio) / sr) if sr else 0.0\n    except Exception as decode_err:\n        try:\n            target.unlink()\n        except OSError:\n            pass\n        raise HTTPException(\n            status_code=400,\n            detail=f\"Could not decode audio: {decode_err}\",\n        ) from decode_err\n","sourceCodeStart":431,"sourceCodeEnd":467,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/routes/generations.py#L431-L467","documentation":"Returned by POST /generate/import when the upload completes but `b''.join(chunks)` yields an empty byte string — i.e. the first read returned no data (empty file) or the file was zero-length. HTTP 400. This is distinct from a missing file: the upload succeeded but contained no bytes.","triggerScenarios":"Uploading a zero-byte file; the client sends an UploadFile with an empty filename and no body; a truncated upload where the stream ends immediately.","commonSituations":"Placeholder/dummy file created by accident; a frontend bug that constructs an empty FormData entry; a file truncated to 0 bytes by a sync tool.","solutions":["Check that the source file has non-zero size on the client before uploading.","If the user selected an empty file by mistake, re-select the actual asset.","Treat 400 'Empty audio file.' as a user-correctable input error, not a server fault."],"exampleFix":"// before\nupload(file);\n\n// after\nif (!file || file.size === 0) { alert('File is empty'); return; }\nupload(file);","handlingStrategy":"validation","validationCode":"if (!file || file.size === 0) { alert('File is empty'); return; }\nawait upload(file);","typeGuard":"function isNonEmptyFile(file) {\n  return file != null && typeof file.size === 'number' && file.size > 0;\n}","tryCatchPattern":"try {\n  const res = await fetch('/generate/import', { method:'POST', body: form });\n  if (res.status === 400) {\n    const d = await res.json();\n    if (/empty/i.test(d.detail)) { alert('File is empty'); return; }\n  }\n} catch (e) { console.error(e); }","preventionTips":["Reject zero-byte files in the file picker.","Validate size > 0 before constructing FormData."],"tags":["fastapi","file-upload","audio","import","empty-file","validation"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}