odysseus-dev/odysseus · error · HTTPException

Upload failed: {e}

Error message

Upload failed: {e}

What it means

500 wrapper raised by POST /api/documents/import-pdf when upload_handler.save_upload(file, client_ip, owner=user) throws an unexpected exception (HTTPException from inside save_upload is passed through unchanged). save_upload is responsible for writing the multipart file to disk, so failures are almost always I/O or storage related; the original exception text follows 'Upload failed:'.

Source

Thrown at routes/document/document_routes.py:266

        # doc (the doc creators below already handle a missing session).
        if session_id:
            db = SessionLocal()
            try:
                _get_session_or_404(db, session_id, user)
            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}")

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Check the server log line 'PDF import save_upload failed: <e>' for the underlying OSError/exception.
  2. Verify the upload directory exists and the service user has write permission (mkdir -p, chown, or fix the volume mount).
  3. Confirm free disk space and quotas on the upload volume.
  4. If the cause is an internal size/validation check thrown as a plain exception, convert it to HTTPException(413/400) inside save_upload so it propagates cleanly.

Example fix

# before
# upload dir never created; save_upload raises PermissionError -> 500 Upload failed
# after
UploadHandler(base_dir, upload_dir)  # ensure upload_dir is created at init:
# os.makedirs(upload_dir, exist_ok=True) inside UploadHandler.__init__
Defensive patterns

Strategy: retry

Validate before calling

async function safeImport(fd) {
  // pre-flight: file present and non-empty
  if (!fd.get('file')?.size) throw new Error('empty file');
  return api.post('/api/documents/import-pdf', fd, { timeout: 120000 });
}

Try / catch

try { meta = await api.post('/api/documents/import-pdf', fd); }
catch (e) {
  if (/Upload failed/.test(e.message)) {
    if (isTransient(e)) await retryWithBackoff(() => api.post('/api/documents/import-pdf', fd), 3);
    else notify('Upload failed: ' + e.message);
  } else throw e;
}

Prevention

When it happens

Trigger: Upload directory does not exist or is not writable by the API process; disk full; the uploaded file exceeds a size cap enforced inside save_upload with a non-HTTP exception; filename encoding problems producing an OSError on write.

Common situations: Containerized deployment where the uploads volume is missing or owned by root; disk quota exhausted on a long-running dev box; SELinux/AppArmor denying writes; very large PDFs hitting an internal limit implemented as ValueError instead of HTTPException.

Related errors


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