odysseus-dev/odysseus · error · HTTPException

Saved PDF could not be located

Error message

Saved PDF could not be located

What it means

500 raised by POST /api/documents/import-pdf when save_upload reported success and returned an id, but _locate_current_user_upload(request, upload_id, user) could not resolve that id to a readable file path for the requesting user. It means the handler stored the file (or its metadata) somewhere the path-resolution step does not look, or ownership scoping filtered it out.

Source

Thrown at routes/document/document_routes.py:271

            finally:
                db.close()

        if upload_handler is None:
            raise HTTPException(500, "Upload handler not configured")

        client_ip = request.client.host if request.client else "unknown"
        try:
            meta = upload_handler.save_upload(file, client_ip, owner=user)
        except HTTPException:
            raise
        except Exception as e:
            logger.error(f"PDF import save_upload failed: {e}")
            raise HTTPException(500, f"Upload failed: {e}")

        upload_id = meta["id"]
        pdf_path = _locate_current_user_upload(request, upload_id, user)
        if not pdf_path:
            raise HTTPException(500, "Saved PDF could not be located")

        title = os.path.splitext(meta.get("original_name") or meta.get("name") or upload_id)[0]
        try:
            body_text = strip_pdf_content_marker(_process_pdf(pdf_path, owner=user))
        except Exception:
            body_text = None

        is_form = False
        try:
            is_form = has_form_fields(pdf_path)
        except Exception as e:
            logger.warning(f"has_form_fields failed for {pdf_path}: {e}")

        if is_form:
            fields = extract_fields(pdf_path)
            save_field_sidecar(pdf_path, fields)
            doc_id = create_form_markdown_document(
                session_id=session_id,

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Confirm the same UploadHandler instance/object is used for both save_upload and path resolution (the one captured by setup_document_routes).
  2. Compare the owner value stored with the upload against get_current_user(request) — they must match exactly, including case.
  3. Inspect the upload directory: does a file named <upload_id> (or mapped to it) actually exist right after the failing request?
  4. Log resolved upload_dir and the candidate paths inside _resolve_user_upload_path to see which side of the mismatch it is.

Example fix

# before
handler_a = UploadHandler('/tmp/uploads')   # used by upload service
handler_b = UploadHandler('/var/uploads')   # injected into routes -> 500
# after
shared = UploadHandler('/var/uploads')
app.include_router(setup_document_routes(session_manager, shared))
Defensive patterns

Strategy: fallback

Try / catch

try { await api.post('/api/documents/import-pdf', fd); }
catch (e) {
  if (/could not be located/.test(e.message)) {
    // storage inconsistency: offer re-upload instead of retry
    offerReupload();
  } else throw e;
}

Prevention

When it happens

Trigger: save_upload writes to a directory different from the handler's configured upload_dir used during resolution; the upload record stores an owner other than the requesting user (auth aliasing, e.g. user vs user:ip key mismatch); async write not yet flushed to disk when resolution runs; file id in metadata ('id') differs from the stored filename.

Common situations: Two UploadHandler instances configured with different base dirs (one injected into routes, another used by the upload service); username normalization (case, domain stripping) applied on one side only; tests that mock save_upload to return a fake id.

Related errors


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