odysseus-dev/odysseus · error · HTTPException

Source PDF {upload_id} not found

Error message

Source PDF {upload_id} not found

What it means

Raised by GET /api/document/{doc_id}/pdf-form-layout with 404 when the document references a source upload_id but _locate_current_user_upload cannot find the corresponding PDF file for the current user. The layout endpoint then has nothing for fitz.open() to render, so it fails before any page-image work.

Source

Thrown at routes/document/document_routes.py:1149

        /page/{n}.png returns at the same DPI) plus the list of form fields
        on that page with their rects translated to image-pixel coordinates.
        Frontend overlays HTML form controls at those positions.
        """
        from src.pdf_form_doc import find_source_upload_id, parse_markdown_to_values, load_field_sidecar

        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)
            upload_id = find_source_upload_id(doc.current_content or "")
            if not upload_id:
                raise HTTPException(400, "Document is not linked to a source PDF")
            pdf_path = _locate_current_user_upload(request, upload_id, user)
            if not pdf_path:
                raise HTTPException(404, f"Source PDF {upload_id} not found")

            fitz = _load_pdf_viewer_fitz()
            schema = load_field_sidecar(pdf_path) or []
            values = parse_markdown_to_values(doc.current_content or "")

            # Group fields by page
            by_page: Dict[int, list] = {}
            for f in schema:
                by_page.setdefault(f["page"], []).append(f)

            scale = _PDF_RENDER_SCALE
            pdf_doc = fitz.open(pdf_path)
            try:
                pages_out = []
                for page_index in range(pdf_doc.page_count):
                    page = pdf_doc[page_index]
                    page_no = page_index + 1
                    pw, ph = page.rect.width, page.rect.height

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Confirm the file for the upload_id in the error exists under the expected uploads path and owner.
  2. Restore the missing PDF (re-upload) or fall back to the plain markdown editor for this document.
  3. Persist the uploads volume across restarts and migrate it together with the database.
  4. Exclude source-upload PDFs from automated file cleanup.

Example fix

# before
pdf_path = _locate_current_user_upload(request, upload_id, user)
if not pdf_path:
    raise HTTPException(404, f"Source PDF {upload_id} not found")

# after
pdf_path = _locate_current_user_upload(request, upload_id, user)
if not pdf_path:
    raise HTTPException(
        404, f"Source PDF {upload_id} not found",
        headers={"X-Reason": "upload-missing"})  # client offers re-upload / plain editor
Defensive patterns

Strategy: fallback

Validate before calling

import os

def source_pdf_reachable(uploads_dir: str, upload_id: str, owner: str) -> bool:
    return any(os.path.exists(p) for p in (
        os.path.join(uploads_dir, owner, upload_id),
        os.path.join(uploads_dir, upload_id),
    ))

Try / catch

try:
    layout = requests.get(f"{base}/api/document/{doc}/pdf-form-layout")
except requests.HTTPError as e:
    if e.response.status_code == 404 and "Source PDF" in e.response.json().get("detail", ""):
        open_plain_editor(doc)  # file missing — degrade gracefully
    else:
        raise

Prevention

When it happens

Trigger: Source PDF deleted from uploads by a cleanup job while the derived document remains; ownership mismatch — the document is visible to the user but the upload belongs to a different owner; uploads directory moved or remounted after deploy.

Common situations: Upload retention pruning; containerized deployments with non-persistent upload volumes; shared/migrated documents whose files did not follow.

Related errors


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