{"record":{"id":"15582537e4545325","repo":"jamiepine/voicebox","slug":"uploaded-file-is-empty","errorCode":null,"errorMessage":"Uploaded file is empty","messagePattern":"Uploaded file is empty","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"backend/routes/captures.py","lineNumber":39,"sourceCode":"UPLOAD_CHUNK_SIZE = 1024 * 1024  # 1 MB\n\n\n@router.post(\"/captures\", response_model=models.CaptureCreateResponse)\nasync def create_capture_endpoint(\n    file: UploadFile = File(...),\n    source: str = Form(\"file\"),\n    language: str | None = Form(None),\n    stt_model: str | None = Form(None),\n    db: Session = Depends(get_db),\n):\n    \"\"\"Upload audio, run STT, persist the capture.\"\"\"\n    chunks = []\n    while chunk := await file.read(UPLOAD_CHUNK_SIZE):\n        chunks.append(chunk)\n    audio_bytes = b\"\".join(chunks)\n\n    if not audio_bytes:\n        raise HTTPException(status_code=400, detail=\"Uploaded file is empty\")\n\n    saved = settings_service.get_capture_settings(db)\n    resolved_stt = stt_model or saved.stt_model\n    if language is None:\n        resolved_language = None if saved.language == \"auto\" else saved.language\n    else:\n        resolved_language = None if language == \"auto\" else language\n\n    try:\n        capture = await captures_service.create_capture(\n            audio_bytes=audio_bytes,\n            filename=file.filename or \"capture.wav\",\n            source=source,\n            language=resolved_language,\n            stt_model=resolved_stt,\n            db=db,\n        )\n    except ValueError as e:","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/routes/captures.py#L21-L57","documentation":"Returned as a 400 from POST /captures when the uploaded audio file stream read zero bytes (the loop `while chunk := await file.read(UPLOAD_CHUNK_SIZE)` produced an empty concatenation). The route streams the upload into memory in chunks and explicitly rejects empty bodies before invoking STT, because a zero-length audio would fail downstream anyway. This is a client-side input error.","triggerScenarios":"POST /captures with a multipart 'file' field that is empty, or a filename pointing to a 0-byte file, or a request where the file part was sent without a body (e.g. frontend sent FormData with an empty Blob).","commonSituations":"Recorder started and stopped with no audio captured (mic permission denied silently, or instant stop); the OS produced a 0-byte temp file; a bug in the client where an empty Blob is appended to FormData.","solutions":["On the client, check the recorded Blob's size > 0 before appending to FormData and posting.","Verify microphone permissions are granted so the recorder actually captures samples.","Ensure the recorder's stop() is only called after audio data has arrived (min-duration guard).","Log file.size and file.type on the client before upload to catch empty captures early."],"exampleFix":"// before\nconst fd = new FormData();\nfd.append('file', blob);\nawait fetch('/captures', { method: 'POST', body: fd });\n// after\nif (!blob || blob.size === 0) {\n  toast('No audio captured');\n  return;\n}\nconst fd = new FormData();\nfd.append('file', blob);\nawait fetch('/captures', { method: 'POST', body: fd });","handlingStrategy":"validation","validationCode":"// Client-side guard before upload.\nif (!blob || blob.size === 0) {\n  throw new Error('No audio recorded; refusing to upload empty file');\n}\nconst fd = new FormData();\nfd.append('file', blob, 'capture.wav');","typeGuard":"function isNonEmptyAudio(blob: Blob | null | undefined): blob is Blob {\n  return blob instanceof Blob && blob.size > 0;\n}","tryCatchPattern":"try {\n  const r = await fetch('/captures', { method: 'POST', body: fd });\n  if (r.status === 400) {\n    const body = await r.json();\n    if (body.detail === 'Uploaded file is empty') {\n      toast('Recording was empty; check mic access');\n      return;\n    }\n    throw new Error(body.detail);\n  }\n} catch (e) { showNetworkError(e); }","preventionTips":["Reject empty recordings on the client before building FormData.","Confirm microphone permission and that the recorder actually received data before stop().","Log blob.size on the client to detect silent zero-length captures."],"tags":["capture","upload","validation","input","fastapi"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}