sipeed/picoclaw · error
failed to create request: %w
Error message
failed to create request: %w
What it means
Returned by OpenAITTSProvider.doSpeechRequest when http.NewRequestWithContext rejects the POST URL. The constructor normalizes apiBase (default https://api.openai.com/v1/audio/speech, forcing the /audio/speech suffix), so failure means the configured base was still not a parseable absolute URL — missing scheme/host after normalization, or characters that break url.Parse.
Source
Thrown at pkg/audio/tts/openai_tts.go:186
responseFormat string,
) (io.ReadCloser, error) {
reqBody := map[string]any{
"model": t.model,
"input": text,
"voice": t.voice,
}
if responseFormat != "" {
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,View on GitHub (pinned to 49183d7e8d)
Solutions
- Set api_base to a full URL with scheme and host, e.g. https://api.openai.com/v1/audio/speech
- url.Parse the configured base at startup and reject invalid values early
- Log the final apiBase after NewOpenAITTSProvider to verify normalization
Example fix
// before
apiBase := `api.openai.com/v1` // normalization cannot add a scheme
// after
apiBase := `https://api.openai.com/v1`
if u, err := url.Parse(apiBase); err != nil || u.Scheme == `` || u.Host == `` {
return fmt.Errorf(`invalid openai tts api_base: %s`, apiBase)
} Defensive patterns
Strategy: validation
Validate before calling
u, err := url.Parse(strings.TrimSpace(apiBase))
if err != nil || u.Scheme == `` || u.Host == `` {
return fmt.Errorf(`invalid openai tts api_base %q: must be an absolute URL`, apiBase)
} Type guard
func isInvalidURL(err error) bool {
return err != nil && strings.Contains(err.Error(), `invalid control character`) ||
err != nil && strings.Contains(err.Error(), `missing protocol scheme`)
} Try / catch
if err != nil {
// configuration bug: fix api_base (full URL with scheme) before retrying
} Prevention
- Configure api_base as a complete URL including https:// and /v1
- url.Parse the base at startup and fail fast
- Log the normalized apiBase after provider construction to confirm the endpoint
When it happens
Trigger: api_base set to 'api.openai.com/v1' without https://; a base with control characters or invalid percent-escapes that falls through the string-based normalization fallback path.
Common situations: Environment/config mistakes where the base URL variable lacks the scheme; typos in local proxy URLs.
Related errors
- create request: %w
- build request: %w
- failed to create request: %w
- Failed to load config
- MCP server ${server.name} requires a URL.
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/762de5dceb3c828e.
Report an issue: GitHub.