vxcontrol/pentagi · error

FlowFiles.InvalidRequest

FlowFiles.InvalidRequest

Error message

at least one uploaded file is required

What it means

UploadFlowFiles requires multipart form parts named 'file'; it collects all such parts and rejects the request with FlowFiles.InvalidRequest when none were provided. The endpoint is strictly multi-upload, so an empty upload is a client-side request error rather than a server fault.

Source

Thrown at backend/pkg/server/services/flow_files.go:164

	}

	c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, flowfiles.MaxUploadRequestSize)
	multipartForm, err := c.MultipartForm()
	if err != nil {
		logger.FromContext(c).WithError(err).WithField("flow_id", flowID).Error("error reading multipart form")
		response.Error(c, response.ErrFlowFilesInvalidRequest, err)
		return
	}

	fileHeaders := multipartForm.File["files"]
	if len(fileHeaders) == 0 {
		fileHeader, formErr := c.FormFile("file")
		if formErr == nil && fileHeader != nil {
			fileHeaders = append(fileHeaders, fileHeader)
		}
	}
	if len(fileHeaders) == 0 {
		err = errors.New("at least one uploaded file is required")
		logger.FromContext(c).WithError(err).WithField("flow_id", flowID).Error("missing uploaded files")
		response.Error(c, response.ErrFlowFilesInvalidRequest, err)
		return
	}
	if len(fileHeaders) > flowfiles.MaxUploadFiles {
		err = fmt.Errorf("too many uploaded files: %d exceeds the maximum allowed count of %d",
			len(fileHeaders), flowfiles.MaxUploadFiles)
		logger.FromContext(c).WithError(err).WithField("flow_id", flowID).Error("too many uploaded files")
		response.Error(c, response.ErrFlowFilesInvalidRequest, err)
		return
	}

	uploadDir := s.flowUploadsDir(flowID)
	if err := os.MkdirAll(uploadDir, 0755); err != nil {
		logger.FromContext(c).WithError(err).WithField("flow_id", flowID).Error("error creating upload directory")
		response.Error(c, response.ErrInternal, err)
		return
	}

View on GitHub (pinned to ea665308ba)

Solutions

  1. Attach at least one multipart part named exactly 'file': curl -F "file=@./report.txt" https://host/api/v1/flows/<id>/files
  2. Verify the Content-Type is multipart/form-data (let the HTTP client set the boundary)
  3. Check the frontend appends to FormData with key 'file' (repeat the key for multiple files)
  4. Confirm no middleware rewrites or drops the request body

Example fix

// before
curl -X POST https://host/api/v1/flows/42/files -F "upload=@a.txt"
// after
curl -X POST https://host/api/v1/flows/42/files -F "file=@a.txt" -F "file=@b.txt"
Defensive patterns

Strategy: validation

Validate before calling

function assertHasFiles(files: FileList | File[] | null): asserts files is NonNullable<typeof files> {
  if (!files || files.length === 0) throw new Error('select at least one file');
}
const fd = new FormData();
for (const f of files) fd.append('file', f); // key must be exactly 'file'

Type guard

function hasMultipartFiles(fd: FormData): boolean {
  return fd.getAll('file').length > 0;
}

Try / catch

try {
  await api.post(`/flows/${flowId}/files`, fd, { headers: { 'Content-Type': 'multipart/form-data' } });
} catch (e) {
  if (e.response?.data?.code === 'FlowFiles.InvalidRequest') {
    alert('Please attach at least one file before uploading.');
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: POST /flows/{flow_id}/files with no 'file' multipart part — e.g. the field is named 'files', 'upload', or 'attachment', or the body was sent as JSON/base64 instead of multipart/form-data.

Common situations: curl -F field-name mismatches; frontend FormData appending under the wrong key; proxies or clients stripping the multipart body; sending an empty FormData object.

Related errors


AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01). Data as JSON: /api/errors/3cf4039600c19100. Report an issue: GitHub.