Billionmail/BillionMail · error

create output dir: %w

Error message

create output dir: %w

What it means

DownloadLipSyncVideo calls os.MkdirAll(cfg.OutputDir, 0755) to ensure the output directory exists before writing the downloaded video. This error wraps the resulting failure — the directory could not be created, typically due to a filesystem permission problem, a read-only volume, or OutputDir colliding with an existing non-directory file. It happens before any network activity.

Source

Thrown at core/internal/service/video_gen/lipsync.go:164

	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("lipsync status API error %d: %s", resp.StatusCode, string(body))
	}

	var result LipSyncResponse
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return nil, fmt.Errorf("decode lipsync status: %w", err)
	}
	return &result, nil
}

// DownloadLipSyncVideo downloads the completed lip sync video to the output directory.
func DownloadLipSyncVideo(ctx context.Context, cfg LipSyncConfig, videoURL, filename string) (string, error) {
	if err := os.MkdirAll(cfg.OutputDir, 0755); err != nil {
		return "", fmt.Errorf("create output dir: %w", err)
	}

	req, err := http.NewRequestWithContext(ctx, "GET", videoURL, nil)
	if err != nil {
		return "", fmt.Errorf("create download request: %w", err)
	}

	resp, err := cfg.doHTTP(req)
	if err != nil {
		return "", fmt.Errorf("download lipsync video: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("download error %d", resp.StatusCode)
	}

	outPath := filepath.Join(cfg.OutputDir, filename)

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Check the wrapped error (os.Stat / os.MkdirAll errno): EACCES → chown/chmod the parent directory or run with a writable user; EROFS → mount a writable volume; ENOTDIR → remove the conflicting file.
  2. Set cfg.OutputDir to an explicitly writable location (e.g. os.TempDir() or a mounted data volume) instead of a hardcoded absolute path.
  3. Validate OutputDir is non-empty and writable at startup (os.MkdirAll + a probe write) before jobs run.
  4. In containers, declare and mount a persistent volume for generated videos and point OutputDir at it.

Example fix

// before: hardcoded dir may not be writable in the deploy environment
cfg := video_gen.DefaultLipSyncConfig("/var/lib/app/videos")
// after: derive from env with fallback and verify writability
dir := os.Getenv("VIDEO_OUTPUT_DIR")
if dir == "" { dir = filepath.Join(os.TempDir(), "videos") }
if err := os.MkdirAll(dir, 0o755); err != nil {
    return fmt.Errorf("output dir %s unusable: %w", dir, err)
}
cfg := video_gen.DefaultLipSyncConfig(dir)
Defensive patterns

Strategy: validation

Validate before calling

func ensureOutputDir(dir string) error {
    if dir == "" { return errors.New("OutputDir is empty") }
    if err := os.MkdirAll(dir, 0o755); err != nil { return err }
    probe := filepath.Join(dir, ".write-probe")
    if err := os.WriteFile(probe, nil, 0o600); err != nil { return err }
    return os.Remove(probe)
}

Try / catch

path, err := video_gen.DownloadLipSyncVideo(ctx, cfg, videoURL, filename)
if err != nil {
    var pe *fs.PathError
    if errors.As(err, &pe) && (errors.Is(pe.Err, fs.ErrPermission) || errors.Is(pe.Err, fs.ErrExist)) {
        return fmt.Errorf("output dir %s not usable (check permissions/mount): %w", cfg.OutputDir, err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling DownloadLipSyncVideo with cfg.OutputDir set to an unwritable path (e.g. /root/... as non-root, a read-only container filesystem), an empty or relative path resolving into a read-only cwd, or a path where a regular file already exists with the directory's name.

Common situations: Docker containers running as non-root with volumes mounted root-owned; OutputDir hardcoded for a dev machine but run in production; missing volume mount so the path falls back to the container overlay which is read-only; OutputDir left empty in DefaultLipSyncConfig usage.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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