odysseus-dev/odysseus · warning · HTTPException
Source PDF could not be located
Error message
Source PDF could not be located
What it means
404 from POST /api/document/{doc_id}/extract-pdf-text: the doc has a valid pdf_source marker with an upload_id, but _locate_current_user_upload cannot resolve that upload to an existing file for the current user. The underlying PDF file backing the document is gone or inaccessible.
Source
Thrown at routes/document/document_routes.py:527
from src.document_processor import _process_pdf, strip_pdf_content_marker
from src.pdf_form_doc import find_source_upload_id
user = get_current_user(request)
db = SessionLocal()
try:
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(View on GitHub (pinned to f9235ebbf1)
Solutions
- Check whether the file for the marker's upload_id still exists in the uploads directory.
- If uploads were lost, re-import the PDF to create a new upload + doc linkage.
- Persist uploads on a durable volume and exclude it from cleanup jobs while docs still reference it.
- Verify the requesting user matches the upload's owner for shared/legacy docs.
Example fix
# before # container restarted with fresh empty /uploads volume; extract returns 404 # after # mount the persistent uploads volume, e.g. # docker run -v document_uploads:/var/app/uploads ...
Defensive patterns
Strategy: fallback
Try / catch
try { return await api.post(`/api/document/${id}/extract-pdf-text`); }
catch (e) {
if (e.status === 404 && /Source PDF/.test(e.message)) {
return offerReimport(id); // file is gone; recreate linkage by re-uploading
}
throw e;
} Prevention
- Store uploads on persistent volumes excluded from GC while docs reference them
- Run retention jobs against doc markers, not just upload age
- Keep database and uploads backups in sync
When it happens
Trigger: Upload files cleaned up by a retention/GC job while the doc row remains; uploads volume not mounted after a container restart/migration; the upload's owner differs from the requesting user so ownership scoping filters the path; file moved by a storage refactor.
Common situations: Restoring a database backup without the uploads directory; ephemeral container storage losing /uploads; cross-user access attempts on a doc whose upload belongs to someone else.
Related errors
- HTTP ${saveRes.status}: ${errBody.substring(0, 120)}
- Upload failed: {e}
- Stream closed before completion
- Server error ' + res.status
- HTTP ${resp.status}${detail ? `: ${detail}` : ''}
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/06ecbc2c17f896c3.
Report an issue: GitHub.