Billionmail/BillionMail · error

create output dir: %w

Error message

create output dir: %w

What it means

CaptureScreenshots first ensures cfg.OutputDir exists via os.MkdirAll. If that fails (permissions, read-only filesystem, path is a file, invalid path), it wraps the OS error with 'create output dir: %w'.

Source

Thrown at core/internal/service/video_gen/screenshots.go:56

// DefaultScreenshotConfig returns config with sensible defaults.
func DefaultScreenshotConfig(websiteURL, businessName, outputDir string) ScreenshotConfig {
	return ScreenshotConfig{
		WebsiteURL:   websiteURL,
		BusinessName: businessName,
		OutputDir:    outputDir,
		Width:        1920,
		Height:       1080,
		Timeout:      30 * time.Second,
	}
}

// CaptureScreenshots takes 3 screenshots of the prospect's web presence:
// homepage, contact/scheduling page, and Google Maps listing.
// Requires: npx playwright (Node.js + Playwright installed).
func CaptureScreenshots(ctx context.Context, cfg ScreenshotConfig) (*ScreenshotResult, error) {
	if err := os.MkdirAll(cfg.OutputDir, 0755); err != nil {
		return nil, fmt.Errorf("create output dir: %w", err)
	}

	result := &ScreenshotResult{
		Homepage: filepath.Join(cfg.OutputDir, "homepage.png"),
		Contact:  filepath.Join(cfg.OutputDir, "contact.png"),
		Google:   filepath.Join(cfg.OutputDir, "google.png"),
	}

	// Build the Playwright script that captures all 3 screenshots
	script := buildPlaywrightScript(cfg, result)

	timeoutCtx, cancel := context.WithTimeout(ctx, cfg.Timeout)
	defer cancel()

	cmd := exec.CommandContext(timeoutCtx, "node", "-e", script)
	cmd.Env = append(os.Environ(), "PLAYWRIGHT_BROWSERS_PATH=0")
	out, err := cmd.CombinedOutput()
	if err != nil {

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Check the wrapped OS error (permission denied / not a directory / no space) for the exact cause
  2. Ensure the output directory path is writable by the process user (chown/chmod or run with correct UID)
  3. Mount a writable volume at the output path in containerized deployments
  4. Verify OutputDir is a directory path, not an existing file

Example fix

// before
cfg := ScreenshotConfig{OutputDir: "/var/lib/app/shots"}
// after
if err := os.MkdirAll("/var/lib/app/shots", 0o755); err != nil {
	log.Fatalf("output dir not writable: %v", err)
}
cfg := ScreenshotConfig{OutputDir: "/var/lib/app/shots"}
Defensive patterns

Strategy: validation

Validate before calling

func ensureWritableDir(path string) error {
	info, err := os.Stat(path)
	if err == nil && !info.IsDir() {
		return fmt.Errorf("%s is a file, not a directory", path)
	}
	if err := os.MkdirAll(path, 0o755); err != nil {
		return err
	}
	f, err := os.CreateTemp(path, ".writecheck")
	if err != nil {
		return err
	}
	f.Close(); os.Remove(f.Name())
	return nil
}

Try / catch

res, err := CaptureScreenshots(ctx, cfg)
if err != nil && strings.HasPrefix(err.Error(), "create output dir:") {
	var perr *fs.PathError
	if errors.As(err, &perr) && errors.Is(perr.Err, fs.ErrPermission) {
		return fmt.Errorf("fix permissions or volume mount for %s", cfg.OutputDir)
	}
}

Prevention

When it happens

Trigger: os.MkdirAll(cfg.OutputDir, 0755) returns an error: parent dirs not writable, OutputDir exists as a regular file, disk full, or a relative path that resolves outside a read-only container layer.

Common situations: Running the pipeline in Docker with a read-only root filesystem and no volume mounted for output; OutputDir pointing at an existing file; running as non-root user without write permission to the configured path.

Related errors


AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05). Data as JSON: /api/errors/7fbcaf222a1b18e6. Report an issue: GitHub.