vxcontrol/pentagi · error

ErrResourcesInvalidRequest

ErrResourcesInvalidRequest

Error message

at least one file is required

What it means

UploadResources requires at least one file in the multipart request. The handler first looks for a `files` (multi-file) field, then falls back to a single `file` field; if neither yields a file header, it returns ErrResourcesInvalidRequest with 'at least one file is required'. There is also an upper bound of resources.MaxUploadFiles.

Source

Thrown at backend/pkg/server/services/resources.go:268

		return
	}

	c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, resources.MaxUploadRequestSize)
	multipartForm, err := c.MultipartForm()
	if err != nil {
		logger.FromContext(c).WithError(err).Error("error reading multipart form for resources upload")
		response.Error(c, response.ErrResourcesInvalidRequest, err)
		return
	}

	fileHeaders := multipartForm.File["files"]
	if len(fileHeaders) == 0 {
		if fh, ferr := c.FormFile("file"); ferr == nil && fh != nil {
			fileHeaders = append(fileHeaders, fh)
		}
	}
	if len(fileHeaders) == 0 {
		response.Error(c, response.ErrResourcesInvalidRequest, errors.New("at least one file is required"))
		return
	}
	if len(fileHeaders) > resources.MaxUploadFiles {
		response.Error(c, response.ErrResourcesInvalidRequest,
			fmt.Errorf("too many files: %d exceeds the limit of %d", len(fileHeaders), resources.MaxUploadFiles))
		return
	}

	if err := resources.EnsureResourcesDir(s.dataDir); err != nil {
		logger.FromContext(c).WithError(err).Error("failed to ensure resources directory")
		response.Error(c, response.ErrInternal, err)
		return
	}
	blobsDir := resources.ResourcesDir(s.dataDir)

	var totalSize int64
	pendingList := make([]pendingResourceUpload, 0, len(fileHeaders))
	seenPaths := make(map[string]struct{}, len(fileHeaders))

View on GitHub (pinned to ea665308ba)

Solutions

  1. Attach at least one file via multipart form field `files` (or a single `file` field) in the POST request
  2. Verify the request Content-Type is multipart/form-data and the client actually appends the File/Blob objects
  3. Check file count stays within resources.MaxUploadFiles

Example fix

// before
curl -X POST https://localhost:8443/api/v1/resources -H 'Authorization: Bearer ...'  # no file sent
// after
curl -X POST https://localhost:8443/api/v1/resources -H 'Authorization: Bearer ...' -F 'files=@report.pdf'
Defensive patterns

Strategy: validation

Validate before calling

if (!files || files.length === 0) throw new Error('select at least one file before uploading');
if (files.length > MAX_UPLOAD_FILES) throw new Error('too many files');

Type guard

function hasFiles(f: FormData): boolean {
  return f.getAll('files').length > 0 || f.get('file') !== null;
}

Try / catch

try {
  await api.post('/resources', formData, { headers: { 'Content-Type': 'multipart/form-data' } });
} catch (e) {
  if (e.response?.data?.code === 'ErrResourcesInvalidRequest' && /file is required/.test(e.response.data.message)) {
    // prompt user to pick a file
  }
}

Prevention

When it happens

Trigger: POSTing the upload endpoint with an empty multipart body, using the wrong form field name (neither `files` nor `file`), sending files as raw body instead of multipart/form-data, or all entries present but empty.

Common situations: Client library serializes files under a different field name; curl invocation omits -F; frontend form not including the File objects; content-type not multipart so FormFile parses nothing.

Related errors


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