chenhg5/cc-connect · error

qwen tts: read audio: %w

Error message

qwen tts: read audio: %w

What it means

After the WAV download response is received (core/tts.go:192), QwenTTS.Synthesize reads the whole body with io.ReadAll. This error wraps a failure while streaming the response body — the connection dropped, the server closed mid-transfer, or a body-limited reader hit its cap.

Source

Thrown at core/tts.go:192

	}
	if result.Output.Audio.URL == "" {
		return nil, "", fmt.Errorf("qwen tts: empty audio URL in response")
	}

	// Download WAV from temporary URL
	audioReq, err := http.NewRequestWithContext(ctx, http.MethodGet, result.Output.Audio.URL, nil)
	if err != nil {
		return nil, "", fmt.Errorf("qwen tts: create download request: %w", err)
	}
	audioResp, err := q.Client.Do(audioReq)
	if err != nil {
		return nil, "", fmt.Errorf("qwen tts: download audio: %w", err)
	}
	defer audioResp.Body.Close()

	wavData, err := io.ReadAll(audioResp.Body)
	if err != nil {
		return nil, "", fmt.Errorf("qwen tts: read audio: %w", err)
	}
	return wavData, "wav", nil
}

// ──────────────────────────────────────────────────────────────
// OpenAITTS — OpenAI-compatible TTS implementation (P1)
// ──────────────────────────────────────────────────────────────

// OpenAITTS implements TextToSpeech using the OpenAI /v1/audio/speech API.
type OpenAITTS struct {
	APIKey  string
	BaseURL string
	Model   string
	Client  *http.Client
}

// NewOpenAITTS creates a new OpenAITTS instance.
func NewOpenAITTS(apiKey, baseURL, model string, client *http.Client) *OpenAITTS {

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Retry Synthesize; transient body-read interruptions are usually temporary
  2. Verify the ctx is not cancelled mid-operation and that no middleware limits the response body size
  3. Increase the client timeout and check proxy idle/connection limits
  4. Wrap the body reader with retry/resume logic if downloads are routinely large

Example fix

// before
wavData, err := io.ReadAll(audioResp.Body)
if err != nil {
    return nil, "", fmt.Errorf("qwen tts: read audio: %w", err)
}
// after
wavData, err := io.ReadAll(audioResp.Body)
if err != nil {
    return nil, "", fmt.Errorf("qwen tts: read audio (%d bytes read): %w", len(wavData), err)
}
if audioResp.StatusCode != http.StatusOK {
    return nil, "", fmt.Errorf("qwen tts: audio download HTTP %d", audioResp.StatusCode)
}
Defensive patterns

Strategy: retry

Validate before calling

// Enforce a sane response size cap before parsing:
const maxWAV = 20 << 20 // 20 MiB
limited := io.LimitReader(audioResp.Body, maxWAV+1)

Try / catch

wav, format, err := tts.Synthesize(ctx, text, opts)
if err != nil && strings.Contains(err.Error(), "read audio") {
    // transient stream failure — retry once, then degrade gracefully
    if wav, format, err = tts.Synthesize(ctx, text, opts); err != nil {
        return fmt.Errorf("tts unavailable: %w", err)
    }
}

Prevention

When it happens

Trigger: Connection reset while reading the audio bytes, server-side abort/timeout mid-stream, context cancellation firing during the read, or a Body limited by http.MaxBytesReader that exceeded its limit.

Common situations: Large audio files over flaky mobile/office networks, an intermediate proxy terminating long transfers, or surrounding code cancelling ctx partway through synthesis.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/21da907b5de11270. Report an issue: GitHub.