sipeed/picoclaw · error

Failed to load catalogs: %v

Error message

Failed to load catalogs: %v

What it means

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.

Source

Thrown at web/backend/api/model_catalog.go:126

	provider = providers.NormalizeProvider(provider)
	store.Entries[key] = &CatalogEntry{
		ID:         key,
		Provider:   provider,
		APIBase:    strings.TrimRight(strings.TrimSpace(apiBase), "/"),
		APIKeyMask: maskAPIKeyValue(apiKey),
		Models:     models,
		FetchedAt:  time.Now().UTC().Format(time.RFC3339),
	}
	return saveCatalogs(store)
}

// handleListCatalogs returns all saved model catalogs.
//
//	GET /api/models/catalog
func (h *Handler) handleListCatalogs(w http.ResponseWriter, r *http.Request) {
	store, err := loadCatalogs()
	if err != nil {
		http.Error(w, fmt.Sprintf("Failed to load catalogs: %v", err), http.StatusInternalServerError)
		return
	}

	entries := make([]*CatalogEntry, 0, len(store.Entries))
	for _, e := range store.Entries {
		entries = append(entries, e)
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]any{
		"entries": entries,
		"total":   len(entries),
	})
}

// handleDeleteCatalog deletes a saved model catalog by ID.
//
//	DELETE /api/models/catalog/{id}

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Check the wrapped error to distinguish parse failure from read failure
  2. Fix the JSON, or delete model_catalogs.json - it is a cache; catalogs are recreated the next time models are fetched per provider/key
  3. Restore read permission for the backend user
  4. Prefer DELETE /api/models/catalog/{id} for pruning so the file is only ever rewritten atomically by the API
Defensive patterns

Strategy: try-catch

Try / catch

const res = await fetch('/api/models/catalog');
if (res.status === 500) {
  const detail = await res.text();
  // model_catalogs.json is a rebuildable cache - offer reset and degrade gracefully
  if (confirm('Saved catalogs file is unreadable (' + detail + '). Reset it?')) {
    await resetCatalogFile();   // delete model_catalogs.json server-side
    return (await fetch('/api/models/catalog')).json();
  }
  return {entries: [], total: 0};
}

Prevention

When it happens

Trigger: 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).

Common situations: Users pruning catalog entries by hand; backup restores of a partially-written file; multi-process access with a non-atomic external writer.

Related errors


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