odysseus-dev/odysseus · error · HTTPException

Extraction failed: {e}

Error message

Extraction failed: {e}

What it means

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.

Source

Thrown at routes/document/document_routes.py:533

            doc = db.query(Document).filter(Document.id == doc_id).first()
            if not doc:
                raise HTTPException(404, "Document not found")
            _verify_doc_owner(db, doc, user)

            content = doc.current_content or ""
            upload_id = find_source_upload_id(content)
            if not upload_id:
                raise HTTPException(400, "Document is not a PDF — no pdf_source marker found")

            pdf_path = _locate_current_user_upload(request, upload_id, user)
            if not pdf_path:
                raise HTTPException(404, "Source PDF could not be located")

            try:
                body_text = strip_pdf_content_marker(_process_pdf(pdf_path, owner=user))
            except Exception as e:
                logger.error(f"extract_pdf_text failed for {pdf_path}: {e}")
                raise HTTPException(500, f"Extraction failed: {e}")

            if not body_text:
                return {"ok": True, "id": doc_id, "extracted": False, "reason": "No readable content"}

            # Preserve everything up through the title (front-matter marker +
            # first H1) and replace the rest with the freshly extracted text.
            head_re = re.compile(r'^(<!--[^>]+-->\s*\n+#[^\n]*\n+)', re.MULTILINE)
            head_match = head_re.match(content)
            head = head_match.group(1) if head_match else (content.splitlines()[0] + "\n\n# " + (doc.title or "PDF") + "\n\n")
            doc.current_content = head + body_text.strip() + "\n"
            doc.version_count = (doc.version_count or 1) + 1
            db.add(DocumentVersion(
                id=str(__import__("uuid").uuid4()),
                document_id=doc_id,
                version_number=doc.version_count,
                content=doc.current_content,
                summary="PDF text re-extracted (OCR)",
                source="ocr",

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Read the logged exception to identify whether pypdf parsing or the VL stage failed.
  2. Validate the PDF externally (qpdf --check, pdftotext) to confirm file integrity; re-upload a clean copy if corrupt.
  3. Decrypt/remove the password before importing if the file is protected.
  4. If the VL path is involved, check its API/dependency configuration and add size/page guards for huge files.

Example fix

# before
resp = requests.post(f"/api/document/{doc_id}/extract-pdf-text")
assert resp.ok  # 500 Extraction failed: Stream has ended unexpectedly
# after
subprocess.run(['qpdf', '--check', pdf_path], check=True)  # pre-validate
resp = requests.post(f"/api/document/{doc_id}/extract-pdf-text")
Defensive patterns

Strategy: validation

Validate before calling

import subprocess

def pdf_is_parseable(path: str) -> bool:
    return subprocess.run(['qpdf', '--check', path], capture_output=True).returncode == 0

if not pdf_is_parseable(path): raise ValueError('corrupt PDF — re-export before import')

Type guard

function looksLikeValidPdf(file: File): boolean {
  return file.size > 0 && (file.type === 'application/pdf' || file.name.endsWith('.pdf'));
}

Try / catch

try { await api.post(`/api/document/${id}/extract-pdf-text`); }
catch (e) { if (e.status === 500 && /Extraction failed/.test(e.message)) { notify('PDF unreadable — please re-upload a clean copy'); return; } throw e; }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/d48c21219c2de5ad. Report an issue: GitHub.