sipeed/picoclaw · error
failed to send request: %w
Error message
failed to send request: %w
What it means
Returned by OpenAITTSProvider.doSpeechRequest when httpClient.Do fails before a response: DNS, refused connections, TLS errors, the 60s client timeout, proxy problems (the client is built via common.NewHTTPClient(proxyURL)), or context cancellation. Transport errors are not retried by the provider — only the response_format rejection path retries.
Source
Thrown at pkg/audio/tts/openai_tts.go:194
reqBody["response_format"] = responseFormat
}
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("Authorization", "Bearer "+t.apiKey)
resp, err := t.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to send request: %w", err)
}
if resp.StatusCode != http.StatusOK {
defer resp.Body.Close()
body, readErr := io.ReadAll(resp.Body)
if readErr != nil {
body = []byte(fmt.Sprintf("(failed to read error body: %v)", readErr))
}
return nil, &openAITTSAPIError{
statusCode: resp.StatusCode,
body: string(body),
}
}
return resp.Body, nil
}
func shouldRetryWithoutResponseFormat(body string) bool {View on GitHub (pinned to 49183d7e8d)
Solutions
- Verify connectivity/proxy reachability to the apiBase host
- Split very long text into chunks or raise the client timeout at construction
- Retry transient transport failures with backoff
- Ensure the caller's context deadline exceeds expected synthesis time
Example fix
// before
stream, err := openaiTTS.Synthesize(ctx, bookChapter)
// after
var stream io.ReadCloser
for _, chunk := range chunkText(bookChapter, 4000) {
s, err := openaiTTS.Synthesize(ctx, chunk)
if err != nil {
return err
}
stream = s
break
} Defensive patterns
Strategy: retry
Validate before calling
u, err := url.Parse(apiBase)
if err != nil {
return fmt.Errorf(`invalid openai tts api_base: %w`, err)
}
host := u.Host
if !strings.Contains(host, `:`) {
host += `:443`
}
conn, err := net.DialTimeout(`tcp`, host, 3*time.Second)
if err != nil {
return fmt.Errorf(`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
err := retryN(3, 2*time.Second, func() error {
var e error
stream, e = provider.Synthesize(ctx, text)
if e != nil && isTransientTransport(e) {
return e
}
return nil
}) Prevention
- Chunk long inputs — the provider's client timeout is fixed at 60s
- Configure a valid proxyURL when egress requires a proxy
- Set caller context deadlines longer than worst-case synthesis time
When it happens
Trigger: api.openai.com (or the configured proxy) unreachable; synthesis of very long input exceeding the fixed 60s timeout; the caller's context canceled or its deadline shorter than synthesis time.
Common situations: Firewalled regions requiring a proxy; long texts (thousands of characters) timing out; short caller deadlines.
Related errors
- failed to send request: %w
- failed to send request: %w
- decode JSON response: %w
- failed to connect to MCP server %q: %w
- failed to reach MCP server %q: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/285d6ede86934133.
Report an issue: GitHub.