{"record":{"id":"06ecbc2c17f896c3","repo":"odysseus-dev/odysseus","slug":"source-pdf-could-not-be-located","errorCode":null,"errorMessage":"Source PDF could not be located","messagePattern":"Source PDF could not be located","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"warning","filePath":"routes/document/document_routes.py","lineNumber":527,"sourceCode":"        from src.document_processor import _process_pdf, strip_pdf_content_marker\n        from src.pdf_form_doc import find_source_upload_id\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\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(","sourceCodeStart":509,"sourceCodeEnd":545,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/document/document_routes.py#L509-L545","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before\n# container restarted with fresh empty /uploads volume; extract returns 404\n# after\n# mount the persistent uploads volume, e.g.\n# docker run -v document_uploads:/var/app/uploads ...","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { return await api.post(`/api/document/${id}/extract-pdf-text`); }\ncatch (e) {\n  if (e.status === 404 && /Source PDF/.test(e.message)) {\n    return offerReimport(id); // file is gone; recreate linkage by re-uploading\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["http-404","pdf","upload","file-lifecycle","storage"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}