sipeed/picoclaw · error

build request: %w

Error message

build request: %w

What it means

http.NewRequest returned an error while constructing the GET to strings.TrimRight(baseURL,"/") + "/models"; the underlying error is wrapped with %w. Because the method and body are fixed, NewRequest practically only fails when the assembled URL cannot be parsed (invalid characters, malformed host or port, missing scheme).

Source

Thrown at cmd/picoclaw/internal/model/online.go:37

	Data []modelEntry `json:"data"`
}

// fetchOpenAIModels GETs <baseURL>/models with Bearer auth and accepts both the
// {data:[…]} envelope and a bare array shape used by various OpenAI-compatible servers.
func fetchOpenAIModels(baseURL, apiKey string) ([]modelEntry, error) {
	if strings.TrimSpace(baseURL) == "" {
		return nil, fmt.Errorf("api base is required")
	}
	if strings.TrimSpace(apiKey) == "" {
		return nil, fmt.Errorf("api key is required")
	}

	url := strings.TrimRight(baseURL, "/") + "/models"

	client := &http.Client{Timeout: 15 * time.Second}
	req, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return nil, fmt.Errorf("build request: %w", err)
	}
	req.Header.Set("Authorization", "Bearer "+apiKey)

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

	if resp.StatusCode != http.StatusOK {
		body, readErr := io.ReadAll(io.LimitReader(resp.Body, 512))
		if readErr != nil {
			return nil, fmt.Errorf("read error response: %w", readErr)
		}
		return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
	}

	body, err := io.ReadAll(resp.Body)

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Read the wrapped cause — Go's url.Error names the exact parse failure (e.g. 'invalid port after host').
  2. Fix the api base to a full URL with scheme and host, e.g. https://api.example.com/v1.
  3. Validate the configured base URL with url.Parse at config load time so bad values fail early.
  4. Check for stray spaces, quotes, or smart quotes copied from docs/chat.

Example fix

// before
apiBase := "api.example.com/v1" // no scheme -> NewRequest fails
// after
apiBase := "https://api.example.com/v1"
Defensive patterns

Strategy: validation

Validate before calling

u, err := url.Parse(strings.TrimRight(baseURL, "/") + "/models")
if err != nil || u.Scheme == "" || u.Host == "" {
    return fmt.Errorf("invalid api base %q", baseURL)
}
req, err := http.NewRequest(http.MethodGet, u.String(), nil)

Type guard

func validBaseURL(s string) bool {
    u, err := url.Parse(s)
    return err == nil && u.Scheme != "" && u.Host != ""
}

Try / catch

req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
    var uerr *url.Error
    if errors.As(err, &uerr) {
        // inspect uerr for the exact parse failure before surfacing
    }
    return fmt.Errorf("build request: %w", err)
}

Prevention

When it happens

Trigger: A baseURL that passes the empty-check but cannot be parsed — e.g. "api.example.com/v1" (no scheme), "http://host:bad-port", or a value containing spaces/control characters — reaches online.go:37 and url.Parse rejects it inside http.NewRequest.

Common situations: Typo in the api base config value (missing https://), stray quotes or trailing punctuation pasted into config.json, or the base URL assembled from an unset/wrong environment variable.

Related errors


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