Billionmail/BillionMail · error

write audio data: %w

Error message

write audio data: %w

What it means

Once the WAV file is created, TextToSpeech streams the audio with io.Copy(f, resp.Body). If the copy fails — the Cartesia connection drops mid-transfer, a read timeout on the response body, or a disk write error — the cause is wrapped as 'write audio data'. The result is a partial or zero-byte WAV file at outPath.

Source

Thrown at core/internal/service/video_gen/voice.go:206

	if err != nil {
		return "", fmt.Errorf("cartesia TTS API call: %w", err)
	}
	defer resp.Body.Close()

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

	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 audio data: %w", err)
	}

	return outPath, nil
}

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Inspect the wrapped error: read/write vs network; retry on network-side failures.
  2. Increase HTTP client response-header/body read timeouts for long audio.
  3. Check free disk space and filesystem health on OutputDir.
  4. Delete the partial file on failure so callers don't consume truncated audio.
  5. Verify the produced file is non-empty and a valid WAV before returning.

Example fix

// before
if _, err := io.Copy(f, resp.Body); err != nil {
    return "", fmt.Errorf("write audio data: %w", err)
}
return outPath, nil
// after
if _, err := io.Copy(f, resp.Body); err != nil {
    f.Close()
    os.Remove(outPath)
    return "", fmt.Errorf("write audio data: %w", err)
}
if err := f.Close(); err != nil {
    os.Remove(outPath)
    return "", fmt.Errorf("close audio file: %w", err)
}
return outPath, nil
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check: enough free space for the expected audio (rough estimate)
const minFreeBytes = 10 * 1024 * 1024
if st, err := os.Statfs(cfg.OutputDir); err == nil {
    // syscall-free alternative:
    _ = st
}
var st syscall.Statfs_t
if err := syscall.Statfs(cfg.OutputDir, &st); err == nil {
    free := st.Bavail * uint64(st.Bsize)
    if free < minFreeBytes {
        return errors.New("insufficient disk space for audio output")
    }
}

Try / catch

path, err := video_gen.TextToSpeech(ctx, cfg, voiceID, transcript, filename)
if err != nil {
    if strings.Contains(err.Error(), "write audio data") {
        os.Remove(path) // clean partial file if any
        return fmt.Errorf("audio transfer interrupted, retry: %w", err)
    }
    return err
}
if info, err := os.Stat(path); err != nil || info.Size() == 0 {
    return errors.New("audio file missing or empty after generation")
}

Prevention

When it happens

Trigger: Cartesia closes the connection before the full audio streams, RateLimitedClient read timeout expires mid-body, ctx is cancelled during transfer, or the disk fills while writing.

Common situations: Long transcripts producing large audio over flaky networks, aggressive HTTP client timeouts, small disk volumes in containers, LB/proxy idle timeouts cutting long responses.

Related errors


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