{"record":{"id":"20aba93225733cf0","repo":"sipeed/picoclaw","slug":"failed-to-load-catalogs-v","errorCode":null,"errorMessage":"Failed to load catalogs: %v","messagePattern":"Failed to load catalogs: (.+?)","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"web/backend/api/model_catalog.go","lineNumber":126,"sourceCode":"\tprovider = providers.NormalizeProvider(provider)\n\tstore.Entries[key] = &CatalogEntry{\n\t\tID:         key,\n\t\tProvider:   provider,\n\t\tAPIBase:    strings.TrimRight(strings.TrimSpace(apiBase), \"/\"),\n\t\tAPIKeyMask: maskAPIKeyValue(apiKey),\n\t\tModels:     models,\n\t\tFetchedAt:  time.Now().UTC().Format(time.RFC3339),\n\t}\n\treturn saveCatalogs(store)\n}\n\n// handleListCatalogs returns all saved model catalogs.\n//\n//\tGET /api/models/catalog\nfunc (h *Handler) handleListCatalogs(w http.ResponseWriter, r *http.Request) {\n\tstore, err := loadCatalogs()\n\tif err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"Failed to load catalogs: %v\", err), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tentries := make([]*CatalogEntry, 0, len(store.Entries))\n\tfor _, e := range store.Entries {\n\t\tentries = append(entries, e)\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tjson.NewEncoder(w).Encode(map[string]any{\n\t\t\"entries\": entries,\n\t\t\"total\":   len(entries),\n\t})\n}\n\n// handleDeleteCatalog deletes a saved model catalog by ID.\n//\n//\tDELETE /api/models/catalog/{id}","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/web/backend/api/model_catalog.go#L108-L144","documentation":"Returned by GET /api/models/catalog when loadCatalogs() (model_catalog.go:72-89) fails to read or parse model_catalogs.json under config.GetHome(). A missing file is fine (returns an empty store); errors require a read failure (permissions, I/O) or invalid JSON in the file - e.g. truncated by an external editor or non-atomic writer, since the API's own saves go through WriteFileAtomic and cannot leave half-written files.","triggerScenarios":"model_catalogs.json hand-edited and left invalid; the file truncated by a crash in an external tool; read permission removed; the home/config directory unavailable (network home, unmounted volume).","commonSituations":"Users pruning catalog entries by hand; backup restores of a partially-written file; multi-process access with a non-atomic external writer.","solutions":["Check the wrapped error to distinguish parse failure from read failure","Fix the JSON, or delete model_catalogs.json - it is a cache; catalogs are recreated the next time models are fetched per provider/key","Restore read permission for the backend user","Prefer DELETE /api/models/catalog/{id} for pruning so the file is only ever rewritten atomically by the API"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"const res = await fetch('/api/models/catalog');\nif (res.status === 500) {\n  const detail = await res.text();\n  // model_catalogs.json is a rebuildable cache - offer reset and degrade gracefully\n  if (confirm('Saved catalogs file is unreadable (' + detail + '). Reset it?')) {\n    await resetCatalogFile();   // delete model_catalogs.json server-side\n    return (await fetch('/api/models/catalog')).json();\n  }\n  return {entries: [], total: 0};\n}","preventionTips":["Never edit model_catalogs.json by hand - use the catalog endpoints so writes stay atomic","Treat the catalog store as disposable cache: refetching models rebuilds it","Alert on repeated 500s here; it signals filesystem trouble in the config home"],"tags":["json","filesystem","model-catalog","go"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}