xpzouying/xiaohongshu-mcp · error

failed to create save path: %v

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.

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)

Solutions

  1. Verify the savePath value is correct, non-empty, and points to a writable location (check config/env before calling NewImageDownloader).
  2. Create or fix the directory permissions manually: mkdir -p <savePath> && chmod 755 <savePath> (and chown to the running user if needed).
  3. If the path exists as a file, remove/rename it so the directory can be created.
  4. In containers/CI, ensure the volume is mounted read-write and the process user has write access.
  5. Wrap the call in a recover() at startup to convert the panic into a graceful startup failure with a clear message.
  6. 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

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.