{"record":{"id":"67625987097de7a0","repo":"odysseus-dev/odysseus","slug":"empty-audio-file","errorCode":null,"errorMessage":"Empty audio file","messagePattern":"Empty audio file","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"routes/stt_routes.py","lineNumber":37,"sourceCode":"        try:\n            return stt_service.get_stats()\n        except Exception as e:\n            logger.error(f\"Failed to get STT stats: {e}\")\n            raise HTTPException(status_code=500, detail=str(e))\n\n    @router.post(\"/transcribe\")\n    async def transcribe_audio(file: UploadFile = File(...)):\n        \"\"\"Transcribe uploaded audio file to text\"\"\"\n        try:\n            if not stt_service.available:\n                raise HTTPException(\n                    status_code=503,\n                    detail={\"message\": \"STT service not available or set to browser mode\"}\n                )\n\n            audio_bytes = await read_upload_limited(file, STT_MAX_AUDIO_BYTES, \"Audio file\")\n            if not audio_bytes:\n                raise HTTPException(status_code=400, detail={\"message\": \"Empty audio file\"})\n\n            text = stt_service.transcribe(audio_bytes)\n            if text is None:\n                raise HTTPException(\n                    status_code=500,\n                    detail={\"message\": \"Transcription failed\"}\n                )\n\n            return {\"text\": text}\n\n        except HTTPException:\n            raise\n        except Exception as e:\n            logger.error(f\"Transcription error: {e}\", exc_info=True)\n            raise HTTPException(\n                status_code=500,\n                detail={\"message\": f\"Transcription failed: {str(e)}\"}\n            )","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/stt_routes.py#L19-L55","documentation":"400 from POST /api/stt/transcribe: read_upload_limited returned no bytes — the uploaded file part is present but its content is zero-length (or drained to zero) after reading within STT_MAX_AUDIO_BYTES. This is an input-shape error raised before the audio ever reaches the STT engine.","triggerScenarios":"Uploading a 0-byte file (touch empty.wav); a client bug creating a Blob/File with no data; a MediaRecorder stream uploaded before any audio was captured (instant stop); a multipart part with an empty body.","commonSituations":"Push-to-talk released instantly producing an empty recording; file picker on a placeholder/empty temp file; upstream proxy stripping the body.","solutions":["Client-side, refuse to upload when the blob size is 0 — check file.size before POSTing.","For recordings, enforce a minimum capture duration before allowing stop/upload.","If a real file uploads as empty, inspect the multipart construction (filename set, read pointer reset, stream not already consumed)."],"exampleFix":"# before\nclient.post(\"/api/stt/transcribe\", files={\"file\": (\"a.wav\", blob, \"audio/wav\")})  # 400\n\n# after\nif blob.size == 0:\n    raise ValueError(\"no audio captured — hold the button longer\")\nclient.post(\"/api/stt/transcribe\", files={\"file\": (\"a.wav\", blob, \"audio/wav\")})","handlingStrategy":"validation","validationCode":"data = audio_blob.getvalue() if hasattr(audio_blob, \"getvalue\") else audio_blob\nassert len(data) > 0, \"refusing to upload zero-length audio\"","typeGuard":"def has_audio_payload(file_obj) -> bool:\n    \"\"\"True when the upload part carries at least one byte.\"\"\"\n    try:\n        pos = file_obj.tell()\n        size = file_obj.seek(0, 2) - pos if pos is not None else file_obj.seek(0, 2)\n        file_obj.seek(0)\n        return size > 0\n    except (OSError, AttributeError):\n        return False","tryCatchPattern":null,"preventionTips":["Check blob/file size client-side before POSTing; skip empty recordings.","Enforce a minimum recording duration on push-to-talk UIs.","Reset file read pointers before constructing multipart bodies."],"tags":["stt","http-400","upload","validation","audio"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}