{"record":{"id":"285d6ede86934133","repo":"sipeed/picoclaw","slug":"failed-to-send-request-w-285d6e","errorCode":null,"errorMessage":"failed to send request: %w","messagePattern":"failed to send request: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/audio/tts/openai_tts.go","lineNumber":194,"sourceCode":"\t\treqBody[\"response_format\"] = responseFormat\n\t}\n\n\tjsonData, err := json.Marshal(reqBody)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to marshal request: %w\", err)\n\t}\n\n\treq, err := http.NewRequestWithContext(ctx, \"POST\", t.apiBase, bytes.NewReader(jsonData))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create request: %w\", err)\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Authorization\", \"Bearer \"+t.apiKey)\n\n\tresp, err := t.httpClient.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to send request: %w\", err)\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tdefer resp.Body.Close()\n\t\tbody, readErr := io.ReadAll(resp.Body)\n\t\tif readErr != nil {\n\t\t\tbody = []byte(fmt.Sprintf(\"(failed to read error body: %v)\", readErr))\n\t\t}\n\t\treturn nil, &openAITTSAPIError{\n\t\t\tstatusCode: resp.StatusCode,\n\t\t\tbody:       string(body),\n\t\t}\n\t}\n\n\treturn resp.Body, nil\n}\n\nfunc shouldRetryWithoutResponseFormat(body string) bool {","sourceCodeStart":176,"sourceCodeEnd":212,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/audio/tts/openai_tts.go#L176-L212","documentation":"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.","triggerScenarios":"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.","commonSituations":"Firewalled regions requiring a proxy; long texts (thousands of characters) timing out; short caller deadlines.","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"],"exampleFix":"// before\nstream, err := openaiTTS.Synthesize(ctx, bookChapter)\n// after\nvar stream io.ReadCloser\nfor _, chunk := range chunkText(bookChapter, 4000) {\n    s, err := openaiTTS.Synthesize(ctx, chunk)\n    if err != nil {\n        return err\n    }\n    stream = s\n    break\n}","handlingStrategy":"retry","validationCode":"u, err := url.Parse(apiBase)\nif err != nil {\n    return fmt.Errorf(`invalid openai tts api_base: %w`, err)\n}\nhost := u.Host\nif !strings.Contains(host, `:`) {\n    host += `:443`\n}\nconn, err := net.DialTimeout(`tcp`, host, 3*time.Second)\nif err != nil {\n    return fmt.Errorf(`tts endpoint unreachable: %w`, err)\n}\n_ = conn.Close()","typeGuard":"func isTransientTransport(err error) bool {\n    var ue *url.Error\n    return errors.As(err, &ue) && !errors.Is(err, context.Canceled)\n}","tryCatchPattern":"err := retryN(3, 2*time.Second, func() error {\n    var e error\n    stream, e = provider.Synthesize(ctx, text)\n    if e != nil && isTransientTransport(e) {\n        return e\n    }\n    return nil\n})","preventionTips":["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"],"tags":["network","timeout","proxy","tts","openai"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}