xpzouying/xiaohongshu-mcp · error

failed to save image

Error message

failed to save image

What it means

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).

Source

Thrown at pkg/downloader/images.go:100

		return "", errors.Wrap(err, "failed to detect file type")
	}

	if !filetype.IsImage(imageData) {
		return "", errors.New("downloaded file is not a valid image")
	}

	// 生成唯一文件名
	fileName := d.generateFileName(imageURL, kind.Extension)
	filePath := filepath.Join(d.savePath, fileName)

	// 如果文件已存在,直接返回路径
	if _, err := os.Stat(filePath); err == nil {
		return filePath, nil
	}

	// 保存到文件
	if err := os.WriteFile(filePath, imageData, 0644); err != nil {
		return "", errors.Wrap(err, "failed to save image")
	}

	return filePath, nil
}

// DownloadImages 批量下载图片
func (d *ImageDownloader) DownloadImages(imageURLs []string) ([]string, error) {
	var localPaths []string
	var errs []error

	for _, imageURL := range imageURLs {
		localPath, err := d.DownloadImage(imageURL)
		if err != nil {
			errs = append(errs, fmt.Errorf("failed to download %s: %w", imageURL, err))
			continue
		}
		localPaths = append(localPaths, localPath)
	}

View on GitHub (pinned to 332d196854)

Solutions

  1. Check the wrapped err with errors.Cause / %v for the exact OS reason (ENOENT, EACCES, ENOSPC) and fix accordingly
  2. Ensure the directory exists before downloading: os.MkdirAll(savePath, 0755) (NewImageDownloader only does it once at construction)
  3. Verify write permission: ls -ld <savePath>, or run with a user that can write; in containers mount a writable volume
  4. Check disk space (df -h) if the cause is ENOSPC

Example fix

// before
if err := os.WriteFile(filePath, imageData, 0644); err != nil {
	return "", errors.Wrap(err, "failed to save image")
}
// after
if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil {
	return "", errors.Wrap(err, "failed to ensure save dir")
}
if err := os.WriteFile(filePath, imageData, 0644); err != nil {
	return "", errors.Wrap(err, "failed to save image")
}
Defensive patterns

Strategy: validation

Validate before calling

// 下载前确保目录存在且可写
if err := os.MkdirAll(savePath, 0755); err != nil {
	return err
}
test := filepath.Join(savePath, ".write_test")
if err := os.WriteFile(test, nil, 0644); err != nil {
	return fmt.Errorf("savePath not writable: %w", err)
}
os.Remove(test)

Type guard

func isSavePathWritable(dir string) bool {
	info, err := os.Stat(dir)
	return err == nil && info.IsDir()
}

Try / catch

path, err := d.DownloadImage(url)
if err != nil {
	var pathErr *os.PathError
	if errors.As(err, &pathErr) {
		log.Printf("filesystem error saving image: %v (dir=%s)", pathErr.Err, savePath)
		// 修复目录后重试一次
		os.MkdirAll(savePath, 0755)
		return d.DownloadImage(url)
	}
	return "", err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Permission denied" / "Failed to write" file errors: why a library can't write its files to disk (EACCES, EPERM, ENOSPC) and how to fix them — this error's family across 43 libraries.

Related errors


AI-assisted analysis of xpzouying/xiaohongshu-mcp@332d196854 (2026-09-05). Data as JSON: /api/errors/80962343e29780e5. Report an issue: GitHub.