sipeed/picoclaw · error

failed to send request: %w

Error message

failed to send request: %w

What it means

Returned by MimoTTSProvider.Synthesize when httpClient.Do fails before any response is received. The error is a *url.Error wrapping DNS failure, connection refused, TLS handshake problems, the fixed 60s client timeout, proxy failures, or context cancellation. Nothing about the request payload is at fault.

Source

Thrown at pkg/audio/tts/mimo_tts.go:124

		"stream": false,
	}

	jsonData, err := json.Marshal(reqBody)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal request: %w", err)
	}

	req, err := http.NewRequestWithContext(ctx, "POST", t.apiBase, bytes.NewReader(jsonData))
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Api-Key", t.apiKey)

	resp, err := t.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read response: %w", err)
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
	}

	var payload struct {
		Choices []struct {
			Message struct {
				Audio struct {
					Data string `json:"data"`
				} `json:"audio"`

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Verify reachability: curl -sS https://api.xiaomimimo.com/v1/chat/completions
  2. If a proxy is required, pass a parseable proxyURL — invalid ones are dropped with only a log warning
  3. Retry with exponential backoff; transient transport errors are the norm
  4. For long texts, split the input or build the provider with a longer client timeout

Example fix

// before
stream, err := mimo.Synthesize(ctx, longText)
// after (retry transient transport errors)
var stream io.ReadCloser
err := retryN(3, time.Second, func() error {
    var e error
    stream, e = mimo.Synthesize(ctx, longText)
    var ue *url.Error
    if errors.As(e, &ue) && !errors.Is(e, context.Canceled) {
        return e // retry
    }
    return nil
})
Defensive patterns

Strategy: retry

Validate before calling

conn, err := net.DialTimeout(`tcp`, `api.xiaomimimo.com:443`, 3*time.Second)
if err != nil {
    return fmt.Errorf(`mimo tts endpoint unreachable: %w`, err)
}
_ = conn.Close()

Type guard

func isTransientTransport(err error) bool {
    var ue *url.Error
    return errors.As(err, &ue) && !errors.Is(err, context.Canceled)
}

Try / catch

var stream io.ReadCloser
err := retryN(3, time.Second, func() error {
    var e error
    stream, e = provider.Synthesize(ctx, text)
    if e != nil && isTransientTransport(e) {
        return e
    }
    return nil // stop on non-transport errors
})

Prevention

When it happens

Trigger: api.xiaomimimo.com unreachable (DNS/firewall); proxyURL passed to NewMimoTTSProvider unparseable (it is silently dropped with a warning); client.Timeout of 60s exceeded for long texts; the caller's context canceled mid-request.

Common situations: Air-gapped or firewalled deployments; corporate proxy requirements; very long synthesis inputs exceeding the fixed timeout; intermittent connectivity.

Related errors


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