{"record":{"id":"b894ecbc92ba93f1","repo":"srbhr/Resume-Matcher","slug":"failed-to-parse-document-please-ensure-it-s-a-val","errorCode":null,"errorMessage":"Failed to parse document. Please ensure it's a valid PDF or DOCX file.","messagePattern":"Failed to parse document\\. Please ensure it's a valid PDF or DOCX file\\.","errorType":"http","errorClass":"HTTPException","httpStatus":422,"severity":"error","filePath":"apps/backend/app/routers/resumes.py","lineNumber":660,"sourceCode":"        )\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 \"\n                \"selectable text, or run OCR first.\"\n            ),\n        )\n\n    # Store in database first with \"processing\" status (atomic master assignment)\n    # original_markdown is preserved permanently for date reference even after\n    # builder saves overwrite `content` with JSON.","sourceCodeStart":642,"sourceCodeEnd":678,"githubUrl":"https://github.com/srbhr/Resume-Matcher/blob/116f9cc3b00e1ac91734a6c2679bf41ea64a0edc/apps/backend/app/routers/resumes.py#L642-L678","documentation":"When parse_document (markitdown-based PDF/DOC → Markdown conversion) raises for any reason, upload_resume logs the real exception server-side and re-raises HTTPException 422 with this generic, action-oriented detail. It is the standard backend pattern: log details, return a generic client message.","triggerScenarios":"POST /api/v1/resumes/upload with a file that passes type/size checks but cannot be converted: corrupt or password-protected PDFs, DRM-protected files, malformed DOCX (renamed .txt or .html), or a markitdown/dependency failure on an exotic PDF structure.","commonSituations":"Password-protected PDFs; files renamed to .pdf but actually images/HTML; encrypted PDFs from secure portals; markitdown version issues after a dependency upgrade; corrupt uploads from flaky networks.","solutions":["Check the backend log 'Document parsing failed: <e>' for the real cause","Open the file locally; if it prompts for a password, remove protection before uploading","Re-export the document as a clean PDF or DOCX from the original application","Verify markitdown and its PDF extras are installed at pinned versions (uv sync)"],"exampleFix":"// before: uploading a password-protected PDF\nPOST /api/v1/resumes/upload file=protected.pdf  -> 422\n// after: strip protection first, then upload\nqpdf --decrypt protected.pdf resume.pdf && curl -F 'file=@resume.pdf;type=application/pdf' http://localhost:8000/api/v1/resumes/upload","handlingStrategy":"validation","validationCode":"// client: sanity-check the file opens as a PDF before upload\nconst buf = new Uint8Array(await file.slice(0, 5).arrayBuffer());\nconst isPdf = String.fromCharCode(...buf) === '%PDF-';\nif (file.name.endsWith('.pdf') && !isPdf) {\n  throw new Error('File is not a real PDF (missing %PDF- header) — re-export it');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await api.postForm('/resumes/upload', form);\n} catch (e) {\n  if (e.response?.status === 422 && /Failed to parse document/.test(e.response?.data?.detail ?? '')) {\n    showToast('Could not read that document — remove password protection and re-export as PDF/DOCX');\n  }\n}","preventionTips":["Remove password/DRM protection before uploading (qpdf --decrypt or print-to-PDF)","Re-export from the source app instead of renaming other formats to .pdf","Check the server log 'Document parsing failed' for the precise markitdown error","Keep markitdown and PDF extras at pinned versions (uv sync) after upgrades"],"tags":["http-422","file-upload","document-parsing","pdf"],"backgroundTag":"document-parse-failed","analyzedSha":"116f9cc3b00e1ac91734a6c2679bf41ea64a0edc","analyzedAt":"2026-08-28T22:51:40.999Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}