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
- Confirm the same UploadHandler instance/object is used for both save_upload and path resolution (the one captured by setup_document_routes).
- Compare the owner value stored with the upload against get_current_user(request) — they must match exactly, including case.
- Inspect the upload directory: does a file named <upload_id> (or mapped to it) actually exist right after the failing request?
- 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
- Use one shared UploadHandler instance for saving and resolution
- Keep the uploads volume on durable storage
- Log upload_id plus resolved path server-side to catch config drift early
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
- Upload failed: {e}
- Skill import failed
- d.error || 'Failed to create calendar'
- Server error ' + res.status
- HTTP ${resp.status}${detail ? `: ${detail}` : ''}
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/c84040981735ae61.
Report an issue: GitHub.