Billionmail/BillionMail · error

write video data: %w

Error message

write video data: %w

What it means

io.Copy(f, resp.Body) failed while streaming the video bytes to disk. This means the transfer was interrupted mid-download — connection reset/timeout by the CDN or rate-limited client, or a local write error (disk full). A partial/truncated lipsync.mp4 may be left on disk since the file is not cleaned up on failure.

Source

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

	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)
	f, err := os.Create(outPath)
	if err != nil {
		return "", fmt.Errorf("create output file: %w", err)
	}
	defer f.Close()

	if _, err := io.Copy(f, resp.Body); err != nil {
		return "", fmt.Errorf("write video data: %w", err)
	}

	return outPath, nil
}

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Check disk space first — a local ENOSPC surfaces here just like a network error.
  2. Wrap the copy in an explicit context/timeout sized for the file size, or remove the tight client timeout for downloads.
  3. Delete the partial file on error so stale truncated videos are never uploaded: os.Remove(outPath) before returning.
  4. Compare downloaded size with Content-Length to detect truncation; retry the whole download if mismatched.
  5. Use a buffered copy with periodic flush or resumable Range requests for very large files over flaky networks.

Example fix

// before
if _, err := io.Copy(f, resp.Body); err != nil {
	return "", fmt.Errorf("write video data: %w", err)
}
// after
if _, err := io.Copy(f, resp.Body); err != nil {
	f.Close()
	os.Remove(outPath) // don't leave a truncated video on disk
	return "", fmt.Errorf("write video data: %w", err)
}
Defensive patterns

Strategy: retry

Validate before calling

// pre-flight checks before download
if free, err := freeDisk(cfg.OutputDir); err == nil && free < minRequiredBytes {
	return fmt.Errorf("insufficient disk: %d bytes free", free)
}
req := http.NewRequestWithContext(ctx, "HEAD", videoURL, nil) // optional: check size via Content-Length

Type guard

func isInterruptedCopy(err error) bool {
	return errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) ||
		errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, syscall.EIO) || errors.Is(err, syscall.ENOSPC)
}

Try / catch

path, err := DownloadLipSyncVideo(ctx, cfg, videoURL, filename)
if err != nil && strings.Contains(err.Error(), "write video data") {
	os.Remove(filepath.Join(cfg.OutputDir, filename)) // remove partial file
	if isInterruptedCopy(err) { /* retry download with fresh context */ }
	return err
}

Prevention

When it happens

Trigger: Reading from resp.Body errors (connection reset, context deadline exceeded during a large download, rate-limited client cutting the connection) or writing to f errors (ENOSPC, I/O error on the mount).

Common situations: Large videos exceeding a proxy/LB idle timeout; context canceled mid-stream when the pipeline deadline hits; disk filling up during the copy; RateLimitedClient's 60s wait/timeout budget too small for multi-hundred-MB downloads.

Related errors


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