{"record":{"id":"b2e95292384d976f","repo":"Billionmail/BillionMail","slug":"create-output-dir-w-b2e952","errorCode":null,"errorMessage":"create output dir: %w","messagePattern":"create output dir: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/video_gen/lipsync.go","lineNumber":164,"sourceCode":"\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tbody, _ := io.ReadAll(resp.Body)\n\t\treturn nil, fmt.Errorf(\"lipsync status API error %d: %s\", resp.StatusCode, string(body))\n\t}\n\n\tvar result LipSyncResponse\n\tif err := json.NewDecoder(resp.Body).Decode(&result); err != nil {\n\t\treturn nil, fmt.Errorf(\"decode lipsync status: %w\", err)\n\t}\n\treturn &result, nil\n}\n\n// DownloadLipSyncVideo downloads the completed lip sync video to the output directory.\nfunc DownloadLipSyncVideo(ctx context.Context, cfg LipSyncConfig, videoURL, filename string) (string, error) {\n\tif err := os.MkdirAll(cfg.OutputDir, 0755); err != nil {\n\t\treturn \"\", fmt.Errorf(\"create output dir: %w\", err)\n\t}\n\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", videoURL, nil)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"create download request: %w\", err)\n\t}\n\n\tresp, err := cfg.doHTTP(req)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"download lipsync video: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"download error %d\", resp.StatusCode)\n\t}\n\n\toutPath := filepath.Join(cfg.OutputDir, filename)","sourceCodeStart":146,"sourceCodeEnd":182,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/video_gen/lipsync.go#L146-L182","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: hardcoded dir may not be writable in the deploy environment\ncfg := video_gen.DefaultLipSyncConfig(\"/var/lib/app/videos\")\n// after: derive from env with fallback and verify writability\ndir := os.Getenv(\"VIDEO_OUTPUT_DIR\")\nif dir == \"\" { dir = filepath.Join(os.TempDir(), \"videos\") }\nif err := os.MkdirAll(dir, 0o755); err != nil {\n    return fmt.Errorf(\"output dir %s unusable: %w\", dir, err)\n}\ncfg := video_gen.DefaultLipSyncConfig(dir)","handlingStrategy":"validation","validationCode":"func ensureOutputDir(dir string) error {\n    if dir == \"\" { return errors.New(\"OutputDir is empty\") }\n    if err := os.MkdirAll(dir, 0o755); err != nil { return err }\n    probe := filepath.Join(dir, \".write-probe\")\n    if err := os.WriteFile(probe, nil, 0o600); err != nil { return err }\n    return os.Remove(probe)\n}","typeGuard":null,"tryCatchPattern":"path, err := video_gen.DownloadLipSyncVideo(ctx, cfg, videoURL, filename)\nif err != nil {\n    var pe *fs.PathError\n    if errors.As(err, &pe) && (errors.Is(pe.Err, fs.ErrPermission) || errors.Is(pe.Err, fs.ErrExist)) {\n        return fmt.Errorf(\"output dir %s not usable (check permissions/mount): %w\", cfg.OutputDir, err)\n    }\n    return err\n}","preventionTips":["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."],"tags":["filesystem","permissions","directory","lipsync"],"backgroundTag":"permission-denied","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}