{"record":{"id":"6b4f915d8922a9c1","repo":"usememos/memos","slug":"invalidargument","errorCode":"InvalidArgument","errorMessage":"filter cannot be empty","messagePattern":"filter cannot be empty","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"server/router/api/v1/attachment_service.go","lineNumber":535,"sourceCode":"\t\t}\n\t\tif err := s.Store.ApplyMemoMutation(ctx, &store.MemoMutation{\n\t\t\tMemoID:               memo.ID,\n\t\t\tMemoCreatorID:        memo.CreatorID,\n\t\t\tExpectedMemoContent:  memo.Content,\n\t\t\tRemovedAttachmentIDs: removedIDs,\n\t\t}); err != nil {\n\t\t\tif errors.Is(err, store.ErrMemoMutationConflict) {\n\t\t\t\treturn status.Errorf(codes.FailedPrecondition, \"memo state changed: %v\", err)\n\t\t\t}\n\t\t\treturn status.Errorf(codes.Internal, \"failed to detach attachments: %v\", err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *APIV1Service) validateAttachmentFilter(ctx context.Context, filterStr string) error {\n\tif filterStr == \"\" {\n\t\treturn errors.New(\"filter cannot be empty\")\n\t}\n\n\tengine, err := filter.DefaultAttachmentEngine()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif _, err := engine.CompileToStatement(ctx, filterStr, filter.RenderOptions{Dialect: s.filterDialect()}); err != nil {\n\t\treturn errors.Wrap(err, \"failed to compile filter\")\n\t}\n\treturn nil\n}\n\n// checkAttachmentAccess verifies the user has permission to access the attachment.\n// For unlinked attachments (no memo), only the creator can access.\n// For linked attachments, access follows the memo's visibility rules.\nfunc (s *APIV1Service) checkAttachmentAccess(ctx context.Context, attachment *store.Attachment) error {\n\t// For unlinked attachments, only the creator can access.","sourceCodeStart":517,"sourceCodeEnd":553,"githubUrl":"https://github.com/usememos/memos/blob/14d757ce1fb31c78590f374bc042f8dbedbc20d7/server/router/api/v1/attachment_service.go#L517-L553","documentation":"validateAttachmentFilter rejects an empty filter string before compiling. Any API that takes an attachment filter (e.g. attachment listing with a filter param) requires a non-empty value; passing \"\" or an unset-but-forwarded parameter fails with InvalidArgument.","triggerScenarios":"Calling the attachment list/search RPC with filter = \"\" — commonly a client that always includes the filter field, building it as `\"tag in [\" + strings.Join(tags, \", \") + \"]\"` which becomes empty when no tags are selected.","commonSituations":"Frontends sending filter unconditionally even when the user applied no filter; string concatenation producing empty strings; API consumers defaulting the field to \"\" instead of omitting it.","solutions":["Omit the filter field (or send a valid non-empty filter) instead of an empty string","Fix client concatenation: only include the filter when the components list is non-empty","If the intent is 'match everything', use a trivially true filter like `id > 0` if the grammar supports it, or leave filter unset"],"exampleFix":"// before\nreq := &apiv1.ListAttachmentsRequest{Filter: ptr(filterString)} // \"\" when no tags\n// after\nif filterString != \"\" { req.Filter = ptr(filterString) }","handlingStrategy":"validation","validationCode":"// Build the request so the filter field is only set when non-empty\nreq := &apiv1.ListAttachmentsRequest{}\nif filterStr != \"\" {\n  req.Filter = &wrapperspb.StringValue{Value: filterStr}\n}\n// Also pre-compile to catch grammar errors early:\nif filterStr != \"\" {\n  if _, err := filter.DefaultAttachmentEngine().CompileToStatement(ctx, filterStr, opts); err != nil {\n    return fmt.Errorf(\"invalid filter: %w\", err)\n  }\n}","typeGuard":null,"tryCatchPattern":"// Map to InvalidArgument with guidance\nif err := s.validateAttachmentFilter(ctx, req.GetFilter()); err != nil {\n  if strings.Contains(err.Error(), \"cannot be empty\") {\n    return status.Errorf(codes.InvalidArgument, \"filter must be a non-empty expression; omit the field entirely for no filtering\")\n  }\n  return status.Errorf(codes.InvalidArgument, \"%v\", err)\n}","preventionTips":["Omit optional filter fields instead of sending empty strings","Guard concatenation-built filters with an emptiness check","Validate filters at authoring time with the same engine"],"tags":["api","filter","validation"],"backgroundTag":null,"analyzedSha":"14d757ce1fb31c78590f374bc042f8dbedbc20d7","analyzedAt":"2026-08-15T09:27:36.538Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}