sipeed/picoclaw · error

HTTP %d: %s

Error message

HTTP %d: %s

What it means

The provider answered with a non-200 status; the message embeds the numeric code plus up to 512 bytes of the response body. This is the server rejecting the request — auth, path, or quota — not a transport failure, and the body preview names the provider's reason.

Source

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

	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)
	if err != nil {
		return nil, fmt.Errorf("read response: %w", err)
	}

	// {"data": [...]} envelope. Distinguish "envelope shape with empty list"
	// from "object without a data key" via Data being non-nil after unmarshal:
	// json.Unmarshal sets Data to []modelEntry{} for `{"data":[]}` but leaves
	// it as nil when "data" is absent or null.
	var envelope modelsAPIResponse
	if err := json.Unmarshal(body, &envelope); err == nil && envelope.Data != nil {
		return envelope.Data, nil
	}

	// Bare-array shape, including `[]`.
	var arr []modelEntry

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Map the status: 401/403 -> fix the API key; 404 -> adjust the api base path (add or drop /v1); 429 -> wait or raise limits; 5xx -> retry later.
  2. Read the embedded body preview — providers usually state the exact problem in it.
  3. Reproduce with curl -H "Authorization: Bearer $KEY" <baseURL>/models to confirm independently of picoclaw.
  4. Confirm the key belongs to the same account/endpoint as the api base.

Example fix

// before: api_base = "https://provider.example.com" (server routes /v1/models -> 404)
// after:  api_base = "https://provider.example.com/v1"
Defensive patterns

Strategy: try-catch

Try / catch

if err != nil && strings.HasPrefix(err.Error(), "HTTP ") {
    code, _ := strconv.Atoi(strings.Fields(err.Error())[1])
    switch {
    case code == 429 || code >= 500:
        retryWithBackoff()
    case code == 401 || code == 403:
        failFast("credential rejected")
    case code == 404:
        failFast("api base path is wrong")
    }
}

Prevention

When it happens

Trigger: Any resp.StatusCode != 200 at online.go:52: 401/403 for a bad or revoked key, 404 when the api base path is wrong (e.g. /v1 present or missing versus what the server routes), 429 when rate-limited, 5xx on provider errors.

Common situations: API key revoked or issued for a different provider, api_base set with/without /v1 while the server expects the opposite, free-tier quota exhausted, or pointing the client at an endpoint that does not serve /models at all.

Related errors


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