sipeed/picoclaw · error

tts synthesize failed: %w

Error message

tts synthesize failed: %w

What it means

Returned by tts.SynthesizeAndStore wrapping any failure from provider.Synthesize — the top-level TTS error. The chain carries the concrete cause: mimo/openai transport errors ('failed to send request'), provider rejections ('API error (status ...)'), JSON decode failures, or missing audio data. Unwrap the chain to see which layer failed.

Source

Thrown at pkg/audio/tts/tts.go:113

	channel string,
	chatID string,
) (string, error) {
	if provider == nil {
		return "", fmt.Errorf("tts provider is not configured")
	}
	if store == nil {
		return "", fmt.Errorf("media store not configured")
	}
	if channel == "" || chatID == "" {
		return "", fmt.Errorf("no target channel/chat available")
	}
	if strings.TrimSpace(text) == "" {
		return "", fmt.Errorf("text is required")
	}

	stream, err := provider.Synthesize(ctx, text)
	if err != nil {
		return "", fmt.Errorf("tts synthesize failed: %w", err)
	}
	defer stream.Close()

	err = os.MkdirAll(media.TempDir(), 0o700)
	if err != nil {
		return "", fmt.Errorf("failed to create media temp dir: %w", err)
	}

	fileExt := ".ogg"
	contentType := "audio/ogg"
	if provider.Name() == "mimo-tts" {
		fileExt = ".mp3"
		contentType = "audio/mpeg"
	}
	if metaProvider, ok := stream.(ttsAudioMetaProvider); ok {
		if ext, ct := metaProvider.AudioFileMeta(); ext != "" && ct != "" {
			fileExt = ext
			contentType = ct

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Unwrap the error chain (errors.As / errors.Is) to classify the root cause before acting
  2. Fix per root cause: 401 → key config, 429/5xx/network → backoff and retry, decode → provider drift
  3. Configure a fallback TTSProvider so voice output survives one provider failing
  4. Retry idempotent synthesis with backoff for transient causes

Example fix

// before
ref, err := tts.SynthesizeAndStore(ctx, p, s, text, name, ch, id)
// after
ref, err := tts.SynthesizeAndStore(ctx, p, s, text, name, ch, id)
if err != nil && strings.HasPrefix(err.Error(), `tts synthesize failed`) && fallback != nil {
    ref, err = tts.SynthesizeAndStore(ctx, fallback, s, text, name, ch, id)
}
Defensive patterns

Strategy: fallback

Type guard

func isTTSWrap(err error) bool {
    return err != nil && strings.HasPrefix(err.Error(), `tts synthesize failed`)
}

Try / catch

if err != nil && isTTSWrap(err) {
    // unwrap: errors.Unwrap(err) yields the provider-level cause (transport / API status / decode)
    if fallback != nil {
        ref, err = tts.SynthesizeAndStore(ctx, fallback, store, text, name, channel, chatID)
    }
}

Prevention

When it happens

Trigger: Any provider-level failure: network outage to api.openai.com or api.xiaomimimo.com, 401/429 from the provider, schema or base64 encoding drift, a model that returns no audio.

Common situations: Production voice pipelines; provider incidents; expired API keys; rate limits during bulk synthesis.

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/890dbc2c93742cdc. Report an issue: GitHub.