{"record":{"id":"36afb7c6dbcddffa","repo":"Billionmail/BillionMail","slug":"error-saving-images-file-v","errorCode":null,"errorMessage":"error saving images file: %v","messagePattern":"error saving images file: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/askai/project.go","lineNumber":900,"sourceCode":"\tHeadingFont         string `json:\"heading_font\"`\n\tLinkFooterColor     string `json:\"link_footer_color\"`\n\tLinkSocialColor     string `json:\"link_social_color\"`\n\tPageBackground      string `json:\"page_background\"`\n\tTextColor           string `json:\"text_color\"`\n}\n\nfunc SaveImagesConfig(Domain string, images []ImageInfo) error {\n\timagesPath := fmt.Sprintf(\"%s/%s/images.json\", PRODUCT_CONFIG_PATH, Domain)\n\tif !public.FileExists(PRODUCT_CONFIG_PATH + \"/\" + Domain) {\n\t\tos.MkdirAll(PRODUCT_CONFIG_PATH+\"/\"+Domain, os.ModePerm)\n\t}\n\tdata, err := json.MarshalIndent(images, \"\", \"  \")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error marshalling images: %v\", err)\n\t}\n\terr = os.WriteFile(imagesPath, data, 0644)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error saving images file: %v\", err)\n\t}\n\treturn nil\n}\n\nfunc ReadImagesConfig(Domain string) ([]ImageInfo, error) {\n\timagesPath := fmt.Sprintf(\"%s/%s/images.json\", PRODUCT_CONFIG_PATH, Domain)\n\tif !public.FileExists(imagesPath) {\n\t\tos.WriteFile(imagesPath, []byte(\"[]\"), 0644)\n\t}\n\tdata, err := os.ReadFile(imagesPath)\n\tif err != nil {\n\t\treturn []ImageInfo{}, fmt.Errorf(\"error reading images configuration file: %v\", err)\n\t}\n\n\tvar images []ImageInfo\n\terr = json.Unmarshal(data, &images)\n\tif err != nil {\n\t\treturn []ImageInfo{}, fmt.Errorf(\"error unmarshalling images configuration: %v\", err)","sourceCodeStart":882,"sourceCodeEnd":918,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/askai/project.go#L882-L918","documentation":"SaveImagesConfig wraps any failure of os.WriteFile when persisting the domain's images.json (under PRODUCT_CONFIG_PATH/<Domain>/). The library throws it whenever the file cannot be marshalled-then-written, typically due to filesystem/permission problems, and it surfaces the underlying OS error via %v.","triggerScenarios":"Calling SaveImagesConfig (directly or via UploadImage/ModifyImage/RemoveImage flows) when the PRODUCT_CONFIG_PATH/<Domain> directory does not exist, the process lacks write permission on images.json, or the disk is full.","commonSituations":"Deployments where the config directory was never created before first save; running the service as a non-root user after files were created by root; read-only container filesystems; full disk on the host.","solutions":["Ensure the target directory exists before writing: os.MkdirAll(fmt.Sprintf(\"%s/%s\", PRODUCT_CONFIG_PATH, Domain), 0755) at the top of SaveImagesConfig.","Check permissions/ownership of PRODUCT_CONFIG_PATH and images.json (chown/chmod to the service user).","Verify the volume is writable and not full (df -h, mount flags).","Log the full path from the wrapped error to confirm PRODUCT_CONFIG_PATH points where you expect."],"exampleFix":"// before\nfunc SaveImagesConfig(Domain string, images []ImageInfo) error {\n\tdata, err := json.MarshalIndent(images, \"\", \"  \")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error marshalling images: %v\", err)\n\t}\n\terr = os.WriteFile(imagesPath, data, 0644)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error saving images file: %v\", err)\n\t}\n// after\nfunc SaveImagesConfig(Domain string, images []ImageInfo) error {\n\tif err := os.MkdirAll(filepath.Join(PRODUCT_CONFIG_PATH, Domain), 0755); err != nil {\n\t\treturn fmt.Errorf(\"error creating images dir: %v\", err)\n\t}\n\tdata, err := json.MarshalIndent(images, \"\", \"  \")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error marshalling images: %v\", err)\n\t}\n\ttmp := imagesPath + \".tmp\"\n\tif err := os.WriteFile(tmp, data, 0644); err != nil {\n\t\treturn fmt.Errorf(\"error saving images file: %v\", err)\n\t}\n\treturn os.Rename(tmp, imagesPath)\n}","handlingStrategy":"validation","validationCode":"dir := filepath.Join(PRODUCT_CONFIG_PATH, Domain)\nif fi, err := os.Stat(dir); err != nil || !fi.IsDir() {\n\treturn fmt.Errorf(\"images dir %s missing\", dir)\n}\nif f, err := os.OpenFile(filepath.Join(dir, \"images.json\"), os.O_WRONLY|os.O_CREATE, 0644); err != nil {\n\treturn fmt.Errorf(\"images.json not writable: %v\", err)\n} else {\n\tf.Close()\n}","typeGuard":"func canWrite(path string) bool {\n\tf, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0644)\n\tif err != nil {\n\t\treturn false\n\t}\n\t_ = f.Close()\n\treturn true\n}","tryCatchPattern":"if err := SaveImagesConfig(domain, images); err != nil {\n\tif strings.Contains(err.Error(), \"error saving images file\") {\n\t\t// handle write failure: check disk/permissions, retry or queue\n\t}\n}","preventionTips":["Always MkdirAll the target directory before first write","Run the service with a dedicated user owning PRODUCT_CONFIG_PATH","Monitor disk space and mount flags on config volumes","Use atomic temp-file + rename writes"],"tags":["filesystem","io","config-persistence"],"backgroundTag":"file-write-failed","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}