odysseus-dev/odysseus · warning · HTTPException
Document is not linked to a source PDF
Error message
Document is not linked to a source PDF
What it means
Raised by POST /api/document/{doc_id}/export-pdf/preview with status 400 when find_source_upload_id(doc.current_content) returns None — the document's markdown content contains no marker linking it to an uploaded source PDF. Only documents created from a PDF form upload (whose content embeds the upload reference) support the export-preview flow.
Source
Thrown at routes/document/document_routes.py:1083
async def export_pdf_preview(doc_id: str, request: Request) -> Dict[str, Any]:
"""Return the field-value mapping that would be written to the PDF.
Frontend shows this in a confirmation modal so the user can spot/fix
any wrong values before triggering the actual download.
"""
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 in uploads")
fields = load_field_sidecar(pdf_path)
if not fields:
raise HTTPException(404, "Field schema sidecar missing for source PDF")
values = parse_markdown_to_values(doc.current_content or "")
field_meta = {f["name"]: f for f in fields}
preview = []
for name, current in values.items():
meta = field_meta.get(name)
if not meta:
continue
preview.append({View on GitHub (pinned to f9235ebbf1)
Solutions
- Only expose the export-PDF action when the document is PDF-derived (the marker is present).
- If the marker was lost to edits, restore the version that still contains the source-upload reference.
- Recreate the document via the original PDF upload flow to re-establish the link.
- Return the user to a plain markdown/file export for non-PDF documents.
Example fix
// before
<button onClick={() => exportPdf(doc)}>Export PDF</button>
// after
const isPdfDoc = /\[\[?source[_ -]?upload:([\w-]+)\]?\]/i.test(doc.current_content || '');
{isPdfDoc && <button onClick={() => exportPdf(doc)}>Export PDF</button>} Defensive patterns
Strategy: type-guard
Validate before calling
import re
SOURCE_MARKER = re.compile(r"source[_ -]?upload[:\s]*([\\w-]+)", re.IGNORECASE)
def is_pdf_derived(content: str | None) -> bool:
return bool(content and SOURCE_MARKER.search(content)) Type guard
def is_pdf_derived(doc: Document | None) -> bool:
"""Type/narrowing guard: only PDF-derived docs support export-preview."""
return bool(doc is not None and doc.current_content and SOURCE_MARKER.search(doc.current_content)) Try / catch
try:
p = requests.post(f"{base}/api/document/{doc}/export-pdf/preview")
except requests.HTTPError as e:
if e.response.status_code == 400: # not PDF-derived
offer_markdown_export(doc)
else:
raise Prevention
- Show the Export-PDF action only for PDF-derived documents.
- Ensure edits/AI rewrites preserve the source-upload marker in content.
- If a restore drops the marker, restore a newer version that has it.
When it happens
Trigger: Running export preview on a free-typed or AI-generated document that never came from a PDF upload; the linking marker in current_content was edited away or reformatted by a save; the document was restored from an old version that predates the PDF link.
Common situations: UI offers the export-PDF action for all documents instead of only PDF-derived ones; heavy content edits strip the source marker.
Related errors
- {key} must be an integer
- Password is required
- msg_id and content are required
- Message is required
- await res.text()
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/718e8949bd495209.
Report an issue: GitHub.