sipeed/picoclaw · error

Failed to save catalogs: %v

Error message

Failed to save catalogs: %v

What it means

Returned by DELETE /api/models/catalog/{id} when saveCatalogs() cannot persist the updated store. saveCatalogs (model_catalog.go:91-98) marshals then calls fileutil.WriteFileAtomic, which writes a temp file in the same directory, fsyncs, chmods 0600, and renames. Failures: temp-file creation (directory permissions, disk full), write/sync errors (disk full, I/O error), or rename failure (Windows file lock by antivirus/indexer, NFS quirks). Because the write is atomic, the original file is untouched when this error fires.

Source

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

	if id == "" {
		http.Error(w, "id is required", http.StatusBadRequest)
		return
	}

	store, err := loadCatalogs()
	if err != nil {
		http.Error(w, fmt.Sprintf("Failed to load catalogs: %v", err), http.StatusInternalServerError)
		return
	}

	if _, ok := store.Entries[id]; !ok {
		http.Error(w, "catalog not found", http.StatusNotFound)
		return
	}

	delete(store.Entries, id)
	if err := saveCatalogs(store); err != nil {
		http.Error(w, fmt.Sprintf("Failed to save catalogs: %v", err), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Check the wrapped error text - it names the failing stage (create/write/rename)
  2. Free disk space or expand the volume if full
  3. Grant the backend user write access to the config home directory
  4. On Windows, exclude the config directory from real-time AV scanning; on NFS, prefer a local dir
  5. Retry the DELETE - atomicity guarantees no partial state, so the same id can be re-deleted safely
Defensive patterns

Strategy: retry

Try / catch

for (let attempt = 0; attempt < 2; attempt++) {
  const res = await fetch(`/api/models/catalog/${encodeURIComponent(id)}`, {method: 'DELETE'});
  if (res.ok || res.status === 404) return;                 // success or already gone
  const text = await res.text();
  if (text.startsWith('Failed to save catalogs')) {
    // atomic write failed but left the store intact - same delete is safe to retry once
    await sleep(800);
    continue;
  }
  throw new Error(text);
}

Prevention

When it happens

Trigger: Disk full on the home/config volume; config.GetHome() directory not writable by the backend user; on Windows, an AV scanner holding the file during rename; NFS/DFS mounts where rename semantics misbehave.

Common situations: Edge devices with small disks filling up; service users missing write permission on the config dir after a security hardening pass; endpoint protection on Windows hosts.

Related errors


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