odysseus-dev/odysseus · warning · HTTPException

No files uploaded

Error message

No files uploaded

What it means

HTTP 400 from POST /api/uploads when the multipart form contains no file parts — FastAPI's File(...) dependency yields an empty list and the handler rejects it before any rate limiting or storage work.

Source

Thrown at routes/upload_routes.py:267

            return image_id
        except Exception as e:
            db.rollback()
            logger.warning("Failed to add chat image upload to gallery: %s", e)
            return None
        finally:
            db.close()
    
    @router.post("")
    async def api_upload(
        request: Request,
        files: List[UploadFile] = File(...),
        session_id: Optional[str] = Form(None),
    ):
        """Upload files with enhanced security and organization."""
        if not isinstance(session_id, str):
            session_id = None
        if not files:
            raise HTTPException(400, "No files uploaded")
            
        client_ip = request.client.host if request.client else "unknown"
        out = []

        # Limit concurrent uploads per IP. Count genuine recent upload events —
        # NOT the number of files in this batch. The previous check summed over
        # `files`, so a single multi-file request counted itself as N concurrent
        # uploads and tripped the limit (issue #1346: "attach more than one file
        # → the model doesn't even see them"). save_upload still enforces the
        # per-minute sliding-window rate limit per file.
        recent_uploads = count_recent_uploads(
            upload_handler.upload_rate_log.get(client_ip, []), time.time()
        )

        if recent_uploads >= upload_handler.max_concurrent_uploads:
            raise HTTPException(
                status_code=429,
                detail=f"Maximum concurrent uploads ({upload_handler.max_concurrent_uploads}) exceeded"

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Append each file as a FormData entry named exactly 'files'
  2. Client-side guard: block submit when no file is selected
  3. Send Content-Type: multipart/form-data — let the browser/curl set it; do not force application/json

Example fix

# before
curl -X POST https://host/api/uploads -F session_id=abc          # 400 No files uploaded
# after
curl -X POST https://host/api/uploads -F session_id=abc -F 'files=@doc.pdf'
Defensive patterns

Strategy: validation

Validate before calling

const files = fileInput.files;
if (!files || files.length === 0) { showUserWarning('Select at least one file'); return; }
const fd = new FormData();
[...files].forEach(f => fd.append('files', f));

Type guard

function hasFiles(files: FileList | null): files is FileList & { length: number } {
  return !!files && files.length > 0;
}

Try / catch

if (resp.status === 400 && body.detail === 'No files uploaded') { fixFormDataFieldNames(); }

Prevention

When it happens

Trigger: Sending the multipart body with zero file fields; using a form field name other than 'files' (the endpoint binds the literal name 'files'); sending JSON instead of multipart/form-data so FastAPI binds no files.

Common situations: Frontend FormData appending files under a different key or forgetting to append before fetch; empty file-input submission without client-side validation; curl command missing -F 'files=@...' parts.

Related errors


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