{"record":{"id":"80962343e29780e5","repo":"xpzouying/xiaohongshu-mcp","slug":"failed-to-save-image","errorCode":null,"errorMessage":"failed to save image","messagePattern":"failed to save image","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/downloader/images.go","lineNumber":100,"sourceCode":"\t\treturn \"\", errors.Wrap(err, \"failed to detect file type\")\n\t}\n\n\tif !filetype.IsImage(imageData) {\n\t\treturn \"\", errors.New(\"downloaded file is not a valid image\")\n\t}\n\n\t// 生成唯一文件名\n\tfileName := d.generateFileName(imageURL, kind.Extension)\n\tfilePath := filepath.Join(d.savePath, fileName)\n\n\t// 如果文件已存在，直接返回路径\n\tif _, err := os.Stat(filePath); err == nil {\n\t\treturn filePath, nil\n\t}\n\n\t// 保存到文件\n\tif err := os.WriteFile(filePath, imageData, 0644); err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to save image\")\n\t}\n\n\treturn filePath, nil\n}\n\n// DownloadImages 批量下载图片\nfunc (d *ImageDownloader) DownloadImages(imageURLs []string) ([]string, error) {\n\tvar localPaths []string\n\tvar errs []error\n\n\tfor _, imageURL := range imageURLs {\n\t\tlocalPath, err := d.DownloadImage(imageURL)\n\t\tif err != nil {\n\t\t\terrs = append(errs, fmt.Errorf(\"failed to download %s: %w\", imageURL, err))\n\t\t\tcontinue\n\t\t}\n\t\tlocalPaths = append(localPaths, localPath)\n\t}","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/xpzouying/xiaohongshu-mcp/blob/332d196854a9eac0d2b8c2c0e3d0cc43139d724c/pkg/downloader/images.go#L82-L118","documentation":"After the image bytes pass format validation, DownloadImage writes them with os.WriteFile into the configured savePath. This error wraps any filesystem failure from that write: the save directory missing/removed at runtime, insufficient permissions, disk full, or the resolved path being invalid (e.g. a file where a directory is expected).","triggerScenarios":"Calling DownloadImage/DownloadImages when: savePath directory was deleted after NewImageDownloader ran; process lacks write permission on savePath; disk quota/full; the generated file path collides with an existing directory entry.","commonSituations":"Running the app in a container with a read-only or not-mounted volume path; savePath configured as a relative path while CWD changed; tmp cleaner removed the directory between program start and download; running as non-root user writing to a root-owned folder.","solutions":["Check the wrapped err with errors.Cause / %v for the exact OS reason (ENOENT, EACCES, ENOSPC) and fix accordingly","Ensure the directory exists before downloading: os.MkdirAll(savePath, 0755) (NewImageDownloader only does it once at construction)","Verify write permission: ls -ld <savePath>, or run with a user that can write; in containers mount a writable volume","Check disk space (df -h) if the cause is ENOSPC"],"exampleFix":"// before\nif err := os.WriteFile(filePath, imageData, 0644); err != nil {\n\treturn \"\", errors.Wrap(err, \"failed to save image\")\n}\n// after\nif err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil {\n\treturn \"\", errors.Wrap(err, \"failed to ensure save dir\")\n}\nif err := os.WriteFile(filePath, imageData, 0644); err != nil {\n\treturn \"\", errors.Wrap(err, \"failed to save image\")\n}","handlingStrategy":"validation","validationCode":"// 下载前确保目录存在且可写\nif err := os.MkdirAll(savePath, 0755); err != nil {\n\treturn err\n}\ntest := filepath.Join(savePath, \".write_test\")\nif err := os.WriteFile(test, nil, 0644); err != nil {\n\treturn fmt.Errorf(\"savePath not writable: %w\", err)\n}\nos.Remove(test)","typeGuard":"func isSavePathWritable(dir string) bool {\n\tinfo, err := os.Stat(dir)\n\treturn err == nil && info.IsDir()\n}","tryCatchPattern":"path, err := d.DownloadImage(url)\nif err != nil {\n\tvar pathErr *os.PathError\n\tif errors.As(err, &pathErr) {\n\t\tlog.Printf(\"filesystem error saving image: %v (dir=%s)\", pathErr.Err, savePath)\n\t\t// 修复目录后重试一次\n\t\tos.MkdirAll(savePath, 0755)\n\t\treturn d.DownloadImage(url)\n\t}\n\treturn \"\", err\n}","preventionTips":["Call os.MkdirAll on the savePath at each batch start, not only in the constructor","Mount a writable volume in containers and verify with a write test at startup","Use absolute savePath to avoid CWD-dependent relative paths","Monitor disk space when downloading in bulk"],"tags":["go","filesystem","io","permissions"],"backgroundTag":"file-write-permission-denied","analyzedSha":"332d196854a9eac0d2b8c2c0e3d0cc43139d724c","analyzedAt":"2026-09-05T22:22:55.988Z","contentChangedAt":"2026-09-05T22:22:55.988Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}