{"record":{"id":"e57cb7bdda2e8b83","repo":"srbhr/Resume-Matcher","slug":"empty-file","errorCode":null,"errorMessage":"Empty file","messagePattern":"Empty file","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"apps/backend/app/routers/resumes.py","lineNumber":653,"sourceCode":"    Optionally parses to structured JSON if LLM is configured.\n    \"\"\"\n    # Validate file type\n    if file.content_type not in ALLOWED_TYPES:\n        raise HTTPException(\n            status_code=400,\n            detail=f\"Invalid file type: {file.content_type}. Allowed: PDF, DOC, DOCX\",\n        )\n\n    # Read and validate size\n    content = await file.read()\n    if len(content) > MAX_FILE_SIZE:\n        raise HTTPException(\n            status_code=413,\n            detail=f\"File too large. Maximum size: {MAX_FILE_SIZE // (1024 * 1024)}MB\",\n        )\n\n    if len(content) == 0:\n        raise HTTPException(status_code=400, detail=\"Empty file\")\n\n    # Convert to markdown\n    try:\n        markdown_content = await parse_document(content, file.filename or \"resume.pdf\")\n    except Exception as e:\n        logger.error(f\"Document parsing failed: {e}\")\n        raise HTTPException(\n            status_code=422,\n            detail=\"Failed to parse document. Please ensure it's a valid PDF or DOCX file.\",\n        )\n\n    # Validate extracted text is not empty (image-based PDFs / scanned documents)\n    if not markdown_content or not markdown_content.strip():\n        raise HTTPException(\n            status_code=422,\n            detail=(\n                \"Could not extract text from the uploaded file. The document may be \"\n                \"image-based or scanned. Please upload a text-based PDF/DOCX with \"","sourceCodeStart":635,"sourceCodeEnd":671,"githubUrl":"https://github.com/srbhr/Resume-Matcher/blob/116f9cc3b00e1ac91734a6c2679bf41ea64a0edc/apps/backend/app/routers/resumes.py#L635-L671","documentation":"upload_resume rejects zero-byte uploads with HTTP 400 and the literal detail 'Empty file'. This catches files that pass the content-type check but contain no data, preventing the parser from being invoked on nothing.","triggerScenarios":"POST /api/v1/resumes/upload where the multipart part has zero bytes: uploading an empty file created by a failed export, a truncated/broken download, or a client sending an empty file field programmatically.","commonSituations":"Browser drop-zone handling an empty placeholder file; a test script posting a dummy empty file; interrupted downloads saved as 0-byte PDFs.","solutions":["Verify the file size locally (ls -l / Get-Item) and re-export/re-download a non-empty document","Add client-side validation to reject 0-byte files before calling the API","Check disk space / export tooling if files are consistently empty"],"exampleFix":"// before: client posts whatever the input holds, even empty\nconst body = new FormData(); body.append('file', fileInput.files[0]);\n// after: guard before upload\nif (file.size === 0) { alert('Selected file is empty'); return; }","handlingStrategy":"validation","validationCode":"// client: reject empty selections before upload\nif (!file || file.size === 0) {\n  throw new Error('Selected file is empty');\n}","typeGuard":"const isNonEmptyFile = (f: File | null | undefined): f is File =>\n  f instanceof File && f.size > 0;","tryCatchPattern":"try {\n  await api.postForm('/resumes/upload', form);\n} catch (e) {\n  if (e.response?.status === 400 && e.response?.data?.detail === 'Empty file') {\n    showToast('That file is empty — pick a valid resume file');\n  }\n}","preventionTips":["Filter 0-byte files out of drop-zone handlers","Verify re-downloaded files are non-empty before uploading","Alert on repeated empty uploads — usually a broken export tool or full disk upstream"],"tags":["http-400","file-upload","empty-file","validation"],"backgroundTag":"empty-file-upload","analyzedSha":"116f9cc3b00e1ac91734a6c2679bf41ea64a0edc","analyzedAt":"2026-08-28T22:51:40.999Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}