{"record":{"id":"7d438d0a7f08cdfe","repo":"odysseus-dev/odysseus","slug":"source-pdf-upload-id-not-found","errorCode":null,"errorMessage":"Source PDF {upload_id} not found","messagePattern":"Source PDF (.+?) not found","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"error","filePath":"routes/document/document_routes.py","lineNumber":1149,"sourceCode":"        /page/{n}.png returns at the same DPI) plus the list of form fields\n        on that page with their rects translated to image-pixel coordinates.\n        Frontend overlays HTML form controls at those positions.\n        \"\"\"\n        from src.pdf_form_doc import find_source_upload_id, parse_markdown_to_values, load_field_sidecar\n\n        user = get_current_user(request)\n        db = SessionLocal()\n        try:\n            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            upload_id = find_source_upload_id(doc.current_content or \"\")\n            if not upload_id:\n                raise HTTPException(400, \"Document is not linked to a source PDF\")\n            pdf_path = _locate_current_user_upload(request, upload_id, user)\n            if not pdf_path:\n                raise HTTPException(404, f\"Source PDF {upload_id} not found\")\n\n            fitz = _load_pdf_viewer_fitz()\n            schema = load_field_sidecar(pdf_path) or []\n            values = parse_markdown_to_values(doc.current_content or \"\")\n\n            # Group fields by page\n            by_page: Dict[int, list] = {}\n            for f in schema:\n                by_page.setdefault(f[\"page\"], []).append(f)\n\n            scale = _PDF_RENDER_SCALE\n            pdf_doc = fitz.open(pdf_path)\n            try:\n                pages_out = []\n                for page_index in range(pdf_doc.page_count):\n                    page = pdf_doc[page_index]\n                    page_no = page_index + 1\n                    pw, ph = page.rect.width, page.rect.height","sourceCodeStart":1131,"sourceCodeEnd":1167,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/document/document_routes.py#L1131-L1167","documentation":"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.","triggerScenarios":"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.","commonSituations":"Upload retention pruning; containerized deployments with non-persistent upload volumes; shared/migrated documents whose files did not follow.","solutions":["Confirm the file for the upload_id in the error exists under the expected uploads path and owner.","Restore the missing PDF (re-upload) or fall back to the plain markdown editor for this document.","Persist the uploads volume across restarts and migrate it together with the database.","Exclude source-upload PDFs from automated file cleanup."],"exampleFix":"# before\npdf_path = _locate_current_user_upload(request, upload_id, user)\nif not pdf_path:\n    raise HTTPException(404, f\"Source PDF {upload_id} not found\")\n\n# after\npdf_path = _locate_current_user_upload(request, upload_id, user)\nif not pdf_path:\n    raise HTTPException(\n        404, f\"Source PDF {upload_id} not found\",\n        headers={\"X-Reason\": \"upload-missing\"})  # client offers re-upload / plain editor","handlingStrategy":"fallback","validationCode":"import os\n\ndef source_pdf_reachable(uploads_dir: str, upload_id: str, owner: str) -> bool:\n    return any(os.path.exists(p) for p in (\n        os.path.join(uploads_dir, owner, upload_id),\n        os.path.join(uploads_dir, upload_id),\n    ))","typeGuard":null,"tryCatchPattern":"try:\n    layout = requests.get(f\"{base}/api/document/{doc}/pdf-form-layout\")\nexcept requests.HTTPError as e:\n    if e.response.status_code == 404 and \"Source PDF\" in e.response.json().get(\"detail\", \"\"):\n        open_plain_editor(doc)  # file missing — degrade gracefully\n    else:\n        raise","preventionTips":["Persist the uploads volume; migrate it with the database.","Never let cleanup remove source PDFs whose derived documents remain active.","Offer the plain editor as fallback when the source file is unrecoverable."],"tags":["fastapi","http-404","pdf-viewer","file-management","uploads"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}