xpzouying/xiaohongshu-mcp · error
failed to create save path
Error message
failed to create save path: %v
What it means
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.
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.
Example fix
// before
downloader := downloader.NewImageDownloader(cfg.ImageSavePath) // panics if path invalid
// after
if err := os.MkdirAll(cfg.ImageSavePath, 0755); err != nil {
log.Fatalf("invalid image save path %q: %v", cfg.ImageSavePath, err)
}
downloader := downloader.NewImageDownloader(cfg.ImageSavePath) Defensive patterns
Strategy: validation
Validate before calling
func ensureSavePath(p string) error {
if p == "" {
return fmt.Errorf("save path is empty")
}
if fi, err := os.Stat(p); err == nil {
if !fi.IsDir() {
return fmt.Errorf("%s exists and is not a directory", p)
}
} else if err := os.MkdirAll(p, 0755); err != nil {
return fmt.Errorf("cannot create %s: %w", p, err)
}
f, err := os.CreateTemp(p, ".writable*")
if err != nil {
return fmt.Errorf("%s is not writable: %w", p, err)
}
f.Close()
os.Remove(f.Name())
return nil
}
// call before: if err := ensureSavePath(cfg.ImageSavePath); err != nil { log.Fatal(err) } Type guard
func isValidSavePath(p string) bool {
if p == "" {
return false
}
fi, err := os.Stat(p)
return err == nil && fi.IsDir()
} Try / catch
func newDownloaderSafe(savePath string) (d *downloader.ImageDownloader, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("image downloader init failed: %v", r)
}
}()
d = downloader.NewImageDownloader(savePath)
return d, nil
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: mkdir permission denied (EACCES): failed to create directory errors explained — this error's family across 32 libraries.
Related errors
AI-assisted analysis of xpzouying/xiaohongshu-mcp@332d196854 (2026-09-05).
Data as JSON: /api/errors/7da06e184364be64.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/downloader/images.go:28
"path/filepath"
"strings"
"time"
"github.com/h2non/filetype"
"github.com/pkg/errors"
)
// ImageDownloader 图片下载器
type ImageDownloader struct {
savePath string
httpClient *http.Client
}
// NewImageDownloader 创建图片下载器
func NewImageDownloader(savePath string) *ImageDownloader {
// 确保保存目录存在
if err := os.MkdirAll(savePath, 0755); err != nil {
panic(fmt.Sprintf("failed to create save path: %v", err))
}
return &ImageDownloader{
savePath: savePath,
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
}
}
// DownloadImage 下载图片
// 返回本地文件路径
func (d *ImageDownloader) DownloadImage(imageURL string) (string, error) {
// 验证URL格式
if !d.isValidImageURL(imageURL) {
return "", errors.New("invalid image URL format")
}
View on GitHub (pinned to 332d196854)