{"record":{"id":"7da06e184364be64","repo":"xpzouying/xiaohongshu-mcp","slug":"failed-to-create-save-path-v","errorCode":null,"errorMessage":"failed to create save path: %v","messagePattern":"failed to create save path: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/downloader/images.go","lineNumber":28,"sourceCode":"\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/h2non/filetype\"\n\t\"github.com/pkg/errors\"\n)\n\n// ImageDownloader 图片下载器\ntype ImageDownloader struct {\n\tsavePath   string\n\thttpClient *http.Client\n}\n\n// NewImageDownloader 创建图片下载器\nfunc NewImageDownloader(savePath string) *ImageDownloader {\n\t// 确保保存目录存在\n\tif err := os.MkdirAll(savePath, 0755); err != nil {\n\t\tpanic(fmt.Sprintf(\"failed to create save path: %v\", err))\n\t}\n\n\treturn &ImageDownloader{\n\t\tsavePath: savePath,\n\t\thttpClient: &http.Client{\n\t\t\tTimeout: 30 * time.Second,\n\t\t},\n\t}\n}\n\n// DownloadImage 下载图片\n// 返回本地文件路径\nfunc (d *ImageDownloader) DownloadImage(imageURL string) (string, error) {\n\t// 验证URL格式\n\tif !d.isValidImageURL(imageURL) {\n\t\treturn \"\", errors.New(\"invalid image URL format\")\n\t}\n","sourceCodeStart":10,"sourceCodeEnd":46,"githubUrl":"https://github.com/xpzouying/xiaohongshu-mcp/blob/332d196854a9eac0d2b8c2c0e3d0cc43139d724c/pkg/downloader/images.go#L10-L46","documentation":"NewImageDownloader panics with 'failed to create save path: %v' when os.MkdirAll(savePath, 0755) fails to create (or verify) the directory where downloaded images will be saved. The library throws this eagerly at constructor time so that a bad save path is caught before any download is attempted. Because it uses panic rather than returning an error, an invalid path will crash the calling program unless recovered.","triggerScenarios":"Calling NewImageDownloader(savePath) with: a path whose parent directories are not writable by the current user; a path that exists as a regular file instead of a directory; a savePath containing invalid characters or an empty string ('' resolves to MkdirAll on '' which fails on most platforms); a filesystem that is read-only or full; or a savePath on a network mount that is unavailable.","commonSituations":"Misconfigured config file pointing the image save directory at a nonexistent/typo path; running the app in a Docker container whose volume mount is missing or read-only; deploying as a non-root service user (e.g. systemd/nobody) that cannot write to a root-owned directory; passing an env var like SAVE_PATH that is unset so it becomes an empty string.","solutions":["Verify the savePath value is correct, non-empty, and points to a writable location (check config/env before calling NewImageDownloader).","Create or fix the directory permissions manually: mkdir -p <savePath> && chmod 755 <savePath> (and chown to the running user if needed).","If the path exists as a file, remove/rename it so the directory can be created.","In containers/CI, ensure the volume is mounted read-write and the process user has write access.","Wrap the call in a recover() at startup to convert the panic into a graceful startup failure with a clear message.","Call os.MkdirAll(savePath, 0755) yourself first and fail fast with your own error handling before constructing the downloader."],"exampleFix":"// before\ndownloader := downloader.NewImageDownloader(cfg.ImageSavePath) // panics if path invalid\n\n// after\nif err := os.MkdirAll(cfg.ImageSavePath, 0755); err != nil {\n\tlog.Fatalf(\"invalid image save path %q: %v\", cfg.ImageSavePath, err)\n}\ndownloader := downloader.NewImageDownloader(cfg.ImageSavePath)","handlingStrategy":"validation","validationCode":"func ensureSavePath(p string) error {\n\tif p == \"\" {\n\t\treturn fmt.Errorf(\"save path is empty\")\n\t}\n\tif fi, err := os.Stat(p); err == nil {\n\t\tif !fi.IsDir() {\n\t\t\treturn fmt.Errorf(\"%s exists and is not a directory\", p)\n\t\t}\n\t} else if err := os.MkdirAll(p, 0755); err != nil {\n\t\treturn fmt.Errorf(\"cannot create %s: %w\", p, err)\n\t}\n\tf, err := os.CreateTemp(p, \".writable*\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"%s is not writable: %w\", p, err)\n\t}\n\tf.Close()\n\tos.Remove(f.Name())\n\treturn nil\n}\n// call before: if err := ensureSavePath(cfg.ImageSavePath); err != nil { log.Fatal(err) }","typeGuard":"func isValidSavePath(p string) bool {\n\tif p == \"\" {\n\t\treturn false\n\t}\n\tfi, err := os.Stat(p)\n\treturn err == nil && fi.IsDir()\n}","tryCatchPattern":"func newDownloaderSafe(savePath string) (d *downloader.ImageDownloader, err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\terr = fmt.Errorf(\"image downloader init failed: %v\", r)\n\t\t}\n\t}()\n\td = downloader.NewImageDownloader(savePath)\n\treturn d, nil\n}","preventionTips":["Pre-create the save directory during deployment/setup and verify permissions for the service user.","Validate path config at application startup, before any component that panics on it.","In containers, mount save-path volumes read-write and run as a user with write access, not arbitrary UIDs.","Never pass an unset env var straight into NewImageDownloader; default or reject empty values.","Add a recover() shim around library constructors that panic instead of returning errors."],"tags":["filesystem","panic","constructor","directory-permission","go"],"backgroundTag":"mkdir-permission-denied","analyzedSha":"332d196854a9eac0d2b8c2c0e3d0cc43139d724c","analyzedAt":"2026-09-05T22:22:55.988Z","contentChangedAt":"2026-09-05T22:22:55.988Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}