sipeed/picoclaw · error

failed to send request: %w

Error message

failed to send request: %w

What it means

Thrown when t.httpClient.Do(req) fails in ElevenLabsTranscriber.Transcribe. The client has a 120-second total timeout, so this wraps transport errors: DNS resolution failure, connection refused/reset, TLS handshake errors, context cancellation/deadline, or the 120s timeout firing on a slow upload. The error wraps *url.Error which itself wraps the cause and can be unwrapped with errors.Is/As.

Source

Thrown at pkg/audio/asr/elevenlabs_transcriber.go:110

	req, err := http.NewRequestWithContext(ctx, "POST", url, &requestBody)
	if err != nil {
		logger.ErrorCF("voice", "Failed to create request", map[string]any{"error": err})
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	req.Header.Set("Content-Type", writer.FormDataContentType())
	req.Header.Set("Xi-Api-Key", t.apiKey)

	logger.DebugCF("voice", "Sending transcription request to ElevenLabs API", map[string]any{
		"url":                url,
		"request_size_bytes": requestBody.Len(),
		"file_size_bytes":    fileInfo.Size(),
	})

	resp, err := t.httpClient.Do(req)
	if err != nil {
		logger.ErrorCF("voice", "Failed to send request", map[string]any{"error": err})
		return nil, fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		logger.ErrorCF("voice", "Failed to read response", map[string]any{"error": err})
		return nil, fmt.Errorf("failed to read response: %w", err)
	}

	if resp.StatusCode != http.StatusOK {
		logger.ErrorCF("voice", "ElevenLabs API error", map[string]any{
			"status_code": resp.StatusCode,
			"response":    string(body),
		})
		return nil, fmt.Errorf("ElevenLabs API error (status %d): %s", resp.StatusCode, string(body))
	}

	logger.DebugCF("voice", "Received response from ElevenLabs API", map[string]any{

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Unwrap the error: net.Error.Timeout() distinguishes timeout from refusal; errors.Is(err, context.Canceled) detects cancellation.
  2. Retry with exponential backoff on transient transport errors (reset, timeout, DNS hiccup).
  3. Set HTTPS_PROXY/HTTP_PROXY or fix DNS in the deployment environment.
  4. For big files on slow links, compress or downsample audio first (e.g. 16kHz mono Opus) so upload fits the 120s window.
  5. Check ctx deadlines set by callers — a 60s caller deadline will surface here as context.DeadlineExceeded.

Example fix

// before
resp, err := transcriber.Transcribe(ctx, path)
if err != nil { return err }

// after
resp, err := transcriber.Transcribe(ctx, path)
if err != nil {
    var netErr net.Error
    if errors.As(err, &netErr) && netErr.Timeout() {
        return retryWithBackoff(ctx, func() (*asr.TranscriptionResponse, error) {
            return transcriber.Transcribe(ctx, path)
        })
    }
    if errors.Is(err, context.Canceled) { return err }
    return err
}
Defensive patterns

Strategy: retry

Validate before calling

// Optional preflight (cheap, catches DNS/proxy/egress issues early):
if err := preflightEndpoint(ctx, "https://api.elevenlabs.io", 2*time.Second); err != nil {
    return fmt.Errorf("cannot reach ElevenLabs: %w", err)
}

Type guard

func isTransientTransport(err error) bool {
    var netErr net.Error
    if errors.As(err, &netErr) && netErr.Timeout() { return true }
    if errors.Is(err, syscall.ECONNRESET) || errors.Is(err, syscall.ECONNREFUSED) { return true }
    if errors.Is(err, context.DeadlineExceeded) { return true }
    return false
}

Try / catch

var lastErr error
for attempt := 0; attempt < 3; attempt++ {
    resp, err := el.Transcribe(ctx, path)
    if err == nil { return resp, nil }
    lastErr = err
    if errors.Is(err, context.Canceled) || !isTransientTransport(err) { break }
    time.Sleep(time.Duration(1<<attempt) * time.Second)
}
return nil, fmt.Errorf("ElevenLabs transcription failed: %w", lastErr)

Prevention

When it happens

Trigger: No network egress to api.elevenlabs.io (air-gapped or firewall); DNS server unreachable; corporate proxy required but not configured (HTTP_PROXY/HTTPS_PROXY); self-signed apiBase certificate failing verification; audio upload slower than 120s (very large files on slow links); ctx cancelled before/during the call.

Common situations: Containers without proxy env vars inside corporate networks; transient ISP/DNS outages; huge WAV uploads over mobile links exceeding the hardcoded 120s; user closes the session mid-transcription.

Related errors


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