{"record":{"id":"8b3f824b825328fa","repo":"Billionmail/BillionMail","slug":"lip-sync-timed-out-after-v","errorCode":null,"errorMessage":"lip sync timed out after %v","messagePattern":"lip sync timed out after (.+?)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/video_gen/orchestrator.go","lineNumber":404,"sourceCode":"\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"voice clone: %w\", err)\n\t\t}\n\t\treturn resp.ID, nil\n\t}\n\n\tif defaultID != \"\" {\n\t\treturn defaultID, nil\n\t}\n\n\treturn \"\", fmt.Errorf(\"no voice configured: set VOICE_SAMPLE_URL or VOICE_DEFAULT_ID\")\n}\n\n// pollLipSync polls lip sync status until completed or timeout.\nfunc pollLipSync(ctx context.Context, cfg LipSyncConfig, jobID string) (string, error) {\n\tdeadline := time.Now().Add(lipSyncTimeout)\n\tfor {\n\t\tif time.Now().After(deadline) {\n\t\t\treturn \"\", fmt.Errorf(\"lip sync timed out after %v\", lipSyncTimeout)\n\t\t}\n\n\t\tresp, err := CheckLipSyncStatus(ctx, cfg, jobID)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"check lip sync status: %w\", err)\n\t\t}\n\n\t\tswitch resp.Status {\n\t\tcase \"completed\":\n\t\t\tif resp.VideoURL == \"\" {\n\t\t\t\treturn \"\", fmt.Errorf(\"lip sync completed but no video URL\")\n\t\t\t}\n\t\t\treturn resp.VideoURL, nil\n\t\tcase \"failed\":\n\t\t\treturn \"\", fmt.Errorf(\"lip sync failed: %s\", resp.Error)\n\t\t}\n\n\t\ttime.Sleep(lipSyncPollDelay)","sourceCodeStart":386,"sourceCodeEnd":422,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/video_gen/orchestrator.go#L386-L422","documentation":"pollLipSync polls the external lip-sync provider until the job completes, but stops after lipSyncTimeout has elapsed. If the provider still has not finished by the deadline, the poll loop aborts with this error.","triggerScenarios":"CheckLipSyncStatus keeps returning a non-terminal status (processing/queued) past lipSyncTimeout while RunPipeline waits; typically for long videos, provider congestion, or a stuck job.","commonSituations":"Provider outage or degraded service; very long source audio; job ID whose upstream request silently failed so it never completes; timeout too short for large inputs.","solutions":["Retry RunPipeline's lip-sync stage; transient provider backlogs usually resolve","Increase lipSyncTimeout to a value appropriate for the video length","Verify the job ID is valid and the lip-sync submission succeeded (check upstream logs)","Check provider status/dashboard for ongoing incidents"],"exampleFix":"// before\nconst lipSyncTimeout = 10 * time.Minute\n// after\nconst lipSyncTimeout = 30 * time.Minute // long videos need more headroom","handlingStrategy":"retry","validationCode":"if lipSyncTimeout < estimatedProcessingTime(videoDurationSec) {\n\treturn fmt.Errorf(\"lipSyncTimeout %v too short for %ds video\", lipSyncTimeout, videoDurationSec)\n}","typeGuard":null,"tryCatchPattern":"url, err := pollLipSync(ctx, cfg, jobID)\nif err != nil && strings.Contains(err.Error(), \"timed out\") {\n\t// check the job once more directly — it may have completed after the deadline\n\treturn CheckLipSyncStatus(ctx, cfg, jobID)\n}","preventionTips":["Size lipSyncTimeout relative to input media duration","Alert on repeated timeouts — they usually indicate provider degradation","Make the timeout configurable via env so it can be tuned without a redeploy","Poll less frequently with backoff to avoid provider throttling"],"tags":["timeout","polling","async-job","external-service"],"backgroundTag":"async-job-timeout","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}