sipeed/picoclaw · error

fetch models: %w

Error message

fetch models: %w

What it means

Generic wrapper inside picoclaw model add around fetchOpenAIModels, which GETs <api-base>/models with Bearer auth and a 15s HTTP timeout. It fires for any listing failure: blank api base or key (reachable when the flags are passed empty, since values are trimmed), invalid URL, network error, non-200 status (body preview included), read failure, or a response body that is neither the {data:[...]} envelope nor a bare JSON array.

Source

Thrown at cmd/picoclaw/internal/model/add.go:106

	alias     string
	modelType string
	stdin     io.Reader
	stdout    io.Writer
}

func runAdd(opt addOptions) error {
	if opt.modelType != "" && opt.modelType != "openai-compatible" {
		return fmt.Errorf("unsupported --type %q (only 'openai-compatible' is supported)", opt.modelType)
	}
	if opt.alias == "" {
		opt.alias = defaultAliasName
	}

	selected := opt.modelID
	if selected == "" {
		entries, err := fetchOpenAIModels(opt.apiBase, opt.apiKey)
		if err != nil {
			return fmt.Errorf("fetch models: %w", err)
		}
		if len(entries) == 0 {
			return fmt.Errorf("no models returned by %s", opt.apiBase)
		}
		selected, err = pickModel(opt.stdin, opt.stdout, entries)
		if err != nil {
			return err
		}
	}

	return upsertModelDefault(opt.apiBase, opt.apiKey, opt.alias, selected, opt.stdout)
}

func pickModel(stdin io.Reader, stdout io.Writer, entries []modelEntry) (string, error) {
	fmt.Fprintf(stdout, "\n%d model(s) available:\n", len(entries))
	for i, m := range entries {
		line := m.ID
		if m.Name != "" && m.Name != m.ID {

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Reproduce the exact call: curl -sS -H 'Authorization: Bearer <key>' <api-base>/models and inspect status and body
  2. Fix --api-base: include the scheme and match the provider-documented prefix (often ending in /v1)
  3. Verify the key is valid and allowed to list models
  4. Skip listing entirely with -m <model-id>, which stores the entry without contacting the server
  5. If the body is not JSON (HTML page), the base URL or proxy is wrong — fix it rather than the key

Example fix

# before
$ picoclaw model add -b api.openai.com/v1 -k sk-...
fetch models: request failed: Get "api.openai.com/v1/models": unsupported protocol scheme ""

# after
$ picoclaw model add -b https://api.openai.com/v1 -k sk-...
Defensive patterns

Strategy: retry

Validate before calling

u, err := url.Parse(strings.TrimSpace(apiBase))
if err != nil || u.Scheme == "" || u.Host == "" {
  return fmt.Errorf("--api-base must be a full URL like https://api.openai.com/v1, got %q", apiBase)
}

Type guard

func isFetchModelsError(err error) bool {
  return err != nil && strings.Contains(err.Error(), "fetch models:")
}

var unreachableErr *url.Error
isNetwork := errors.As(err, &unreachableErr) // inside the wrapped chain

Try / catch

var entries []modelEntry
var err error
for attempt := 0; attempt < 3; attempt++ {
  entries, err = fetchOpenAIModels(apiBase, apiKey)
  var ue *url.Error
  if err == nil || !errors.As(err, &ue) {
    break // retry only transport-level failures; HTTP 4xx are permanent
  }
  time.Sleep(time.Duration(attempt+1) * 500 * time.Millisecond)
}

Prevention

When it happens

Trigger: -b '' or -k ' ' passed as blank/whitespace; missing scheme in the base (api.openai.com/v1); wrong /v1 prefix so GET <base>/models 404s; 401/403 from an invalid key; self-signed TLS; HTML error pages or other non-JSON bodies the shape parser rejects.

Common situations: Providers whose documented base needs /v1 appended or removed; keys from a different account or project; corporate proxies rewriting responses; gateways returning HTML login pages; offline or firewalled environments.

Related errors


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