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
- 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.
- Set cfg.OutputDir to an explicitly writable location (e.g. os.TempDir() or a mounted data volume) instead of a hardcoded absolute path.
- Validate OutputDir is non-empty and writable at startup (os.MkdirAll + a probe write) before jobs run.
- 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
- Validate OutputDir writability at startup, before any jobs run.
- In Docker, mount a writable volume and run the process as a user that owns it.
- Never hardcode absolute paths from dev machines; use env-configured dirs with a tmp fallback.
- Ensure no regular file exists at the OutputDir path.
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
- create output dir: %w
- create thumbnail dir: %w
- failed to send confirmation email: %w
- failed to save private key: %v
- error reading project configuration file: %v
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/b2e95292384d976f.
Report an issue: GitHub.