{"record":{"id":"0c957f045d0c951b","repo":"sipeed/picoclaw","slug":"fetch-models-w","errorCode":null,"errorMessage":"fetch models: %w","messagePattern":"fetch models: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/picoclaw/internal/model/add.go","lineNumber":106,"sourceCode":"\talias     string\n\tmodelType string\n\tstdin     io.Reader\n\tstdout    io.Writer\n}\n\nfunc runAdd(opt addOptions) error {\n\tif opt.modelType != \"\" && opt.modelType != \"openai-compatible\" {\n\t\treturn fmt.Errorf(\"unsupported --type %q (only 'openai-compatible' is supported)\", opt.modelType)\n\t}\n\tif opt.alias == \"\" {\n\t\topt.alias = defaultAliasName\n\t}\n\n\tselected := opt.modelID\n\tif selected == \"\" {\n\t\tentries, err := fetchOpenAIModels(opt.apiBase, opt.apiKey)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"fetch models: %w\", err)\n\t\t}\n\t\tif len(entries) == 0 {\n\t\t\treturn fmt.Errorf(\"no models returned by %s\", opt.apiBase)\n\t\t}\n\t\tselected, err = pickModel(opt.stdin, opt.stdout, entries)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn upsertModelDefault(opt.apiBase, opt.apiKey, opt.alias, selected, opt.stdout)\n}\n\nfunc pickModel(stdin io.Reader, stdout io.Writer, entries []modelEntry) (string, error) {\n\tfmt.Fprintf(stdout, \"\\n%d model(s) available:\\n\", len(entries))\n\tfor i, m := range entries {\n\t\tline := m.ID\n\t\tif m.Name != \"\" && m.Name != m.ID {","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/cmd/picoclaw/internal/model/add.go#L88-L124","documentation":"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.","triggerScenarios":"-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.","commonSituations":"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.","solutions":["Reproduce the exact call: curl -sS -H 'Authorization: Bearer <key>' <api-base>/models and inspect status and body","Fix --api-base: include the scheme and match the provider-documented prefix (often ending in /v1)","Verify the key is valid and allowed to list models","Skip listing entirely with -m <model-id>, which stores the entry without contacting the server","If the body is not JSON (HTML page), the base URL or proxy is wrong — fix it rather than the key"],"exampleFix":"# before\n$ picoclaw model add -b api.openai.com/v1 -k sk-...\nfetch models: request failed: Get \"api.openai.com/v1/models\": unsupported protocol scheme \"\"\n\n# after\n$ picoclaw model add -b https://api.openai.com/v1 -k sk-...","handlingStrategy":"retry","validationCode":"u, err := url.Parse(strings.TrimSpace(apiBase))\nif err != nil || u.Scheme == \"\" || u.Host == \"\" {\n  return fmt.Errorf(\"--api-base must be a full URL like https://api.openai.com/v1, got %q\", apiBase)\n}","typeGuard":"func isFetchModelsError(err error) bool {\n  return err != nil && strings.Contains(err.Error(), \"fetch models:\")\n}\n\nvar unreachableErr *url.Error\nisNetwork := errors.As(err, &unreachableErr) // inside the wrapped chain","tryCatchPattern":"var entries []modelEntry\nvar err error\nfor attempt := 0; attempt < 3; attempt++ {\n  entries, err = fetchOpenAIModels(apiBase, apiKey)\n  var ue *url.Error\n  if err == nil || !errors.As(err, &ue) {\n    break // retry only transport-level failures; HTTP 4xx are permanent\n  }\n  time.Sleep(time.Duration(attempt+1) * 500 * time.Millisecond)\n}","preventionTips":["Always include the scheme and the provider-documented prefix (usually ending in /v1)","curl the /models endpoint once before scripting against it","Prefer -m <model-id> to skip the network round-trip when you know the id","Watch for proxies returning HTML with status 200 — fix the base URL, not the key"],"tags":["network","model","cli","http","config"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}