{"record":{"id":"d48c21219c2de5ad","repo":"odysseus-dev/odysseus","slug":"extraction-failed-e","errorCode":null,"errorMessage":"Extraction failed: {e}","messagePattern":"Extraction failed: (.+?)","errorType":"http","errorClass":"HTTPException","httpStatus":500,"severity":"error","filePath":"routes/document/document_routes.py","lineNumber":533,"sourceCode":"            doc = db.query(Document).filter(Document.id == doc_id).first()\n            if not doc:\n                raise HTTPException(404, \"Document not found\")\n            _verify_doc_owner(db, doc, user)\n\n            content = doc.current_content or \"\"\n            upload_id = find_source_upload_id(content)\n            if not upload_id:\n                raise HTTPException(400, \"Document is not a PDF — no pdf_source marker found\")\n\n            pdf_path = _locate_current_user_upload(request, upload_id, user)\n            if not pdf_path:\n                raise HTTPException(404, \"Source PDF could not be located\")\n\n            try:\n                body_text = strip_pdf_content_marker(_process_pdf(pdf_path, owner=user))\n            except Exception as e:\n                logger.error(f\"extract_pdf_text failed for {pdf_path}: {e}\")\n                raise HTTPException(500, f\"Extraction failed: {e}\")\n\n            if not body_text:\n                return {\"ok\": True, \"id\": doc_id, \"extracted\": False, \"reason\": \"No readable content\"}\n\n            # Preserve everything up through the title (front-matter marker +\n            # first H1) and replace the rest with the freshly extracted text.\n            head_re = re.compile(r'^(<!--[^>]+-->\\s*\\n+#[^\\n]*\\n+)', re.MULTILINE)\n            head_match = head_re.match(content)\n            head = head_match.group(1) if head_match else (content.splitlines()[0] + \"\\n\\n# \" + (doc.title or \"PDF\") + \"\\n\\n\")\n            doc.current_content = head + body_text.strip() + \"\\n\"\n            doc.version_count = (doc.version_count or 1) + 1\n            db.add(DocumentVersion(\n                id=str(__import__(\"uuid\").uuid4()),\n                document_id=doc_id,\n                version_number=doc.version_count,\n                content=doc.current_content,\n                summary=\"PDF text re-extracted (OCR)\",\n                source=\"ocr\",","sourceCodeStart":515,"sourceCodeEnd":551,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/document/document_routes.py#L515-L551","documentation":"500 from POST /api/document/{doc_id}/extract-pdf-text: the PDF was located, but _process_pdf(pdf_path, owner=user) raised while running text extraction (pypdf parse and/or the vision-language fallback). The underlying exception is logged as 'extract_pdf_text failed for <path>: <e>' and surfaced in the message.","triggerScenarios":"Corrupt or malformed PDF that pypdf cannot parse (EOF marker missing, broken xref); encrypted/password-protected PDF; the VL fallback failing on oversized/scanned pages; missing native dependency for the extraction pipeline.","commonSituations":"Users uploading truncated files (interrupted transfers); password-protected forms; very large scanned PDFs exhausting memory/time in the VL path; library version change altering pypdf behavior.","solutions":["Read the logged exception to identify whether pypdf parsing or the VL stage failed.","Validate the PDF externally (qpdf --check, pdftotext) to confirm file integrity; re-upload a clean copy if corrupt.","Decrypt/remove the password before importing if the file is protected.","If the VL path is involved, check its API/dependency configuration and add size/page guards for huge files."],"exampleFix":"# before\nresp = requests.post(f\"/api/document/{doc_id}/extract-pdf-text\")\nassert resp.ok  # 500 Extraction failed: Stream has ended unexpectedly\n# after\nsubprocess.run(['qpdf', '--check', pdf_path], check=True)  # pre-validate\nresp = requests.post(f\"/api/document/{doc_id}/extract-pdf-text\")","handlingStrategy":"validation","validationCode":"import subprocess\n\ndef pdf_is_parseable(path: str) -> bool:\n    return subprocess.run(['qpdf', '--check', path], capture_output=True).returncode == 0\n\nif not pdf_is_parseable(path): raise ValueError('corrupt PDF — re-export before import')","typeGuard":"function looksLikeValidPdf(file: File): boolean {\n  return file.size > 0 && (file.type === 'application/pdf' || file.name.endsWith('.pdf'));\n}","tryCatchPattern":"try { await api.post(`/api/document/${id}/extract-pdf-text`); }\ncatch (e) { if (e.status === 500 && /Extraction failed/.test(e.message)) { notify('PDF unreadable — please re-upload a clean copy'); return; } throw e; }","preventionTips":["Validate PDF integrity before import","Reject encrypted PDFs early with a clear message","Cap pages/size before invoking the VL extraction path"],"tags":["pdf","pypdf","extraction","http-500","file-corruption"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}