{"record":{"id":"984f719860dead84","repo":"sipeed/picoclaw","slug":"failed-to-save-catalogs-v","errorCode":null,"errorMessage":"Failed to save catalogs: %v","messagePattern":"Failed to save catalogs: (.+?)","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"web/backend/api/model_catalog.go","lineNumber":165,"sourceCode":"\tif id == \"\" {\n\t\thttp.Error(w, \"id is required\", http.StatusBadRequest)\n\t\treturn\n\t}\n\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\tif _, ok := store.Entries[id]; !ok {\n\t\thttp.Error(w, \"catalog not found\", http.StatusNotFound)\n\t\treturn\n\t}\n\n\tdelete(store.Entries, id)\n\tif err := saveCatalogs(store); err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"Failed to save catalogs: %v\", err), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tjson.NewEncoder(w).Encode(map[string]string{\"status\": \"ok\"})\n}\n","sourceCodeStart":147,"sourceCodeEnd":172,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/web/backend/api/model_catalog.go#L147-L172","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check the wrapped error text - it names the failing stage (create/write/rename)","Free disk space or expand the volume if full","Grant the backend user write access to the config home directory","On Windows, exclude the config directory from real-time AV scanning; on NFS, prefer a local dir","Retry the DELETE - atomicity guarantees no partial state, so the same id can be re-deleted safely"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"for (let attempt = 0; attempt < 2; attempt++) {\n  const res = await fetch(`/api/models/catalog/${encodeURIComponent(id)}`, {method: 'DELETE'});\n  if (res.ok || res.status === 404) return;                 // success or already gone\n  const text = await res.text();\n  if (text.startsWith('Failed to save catalogs')) {\n    // atomic write failed but left the store intact - same delete is safe to retry once\n    await sleep(800);\n    continue;\n  }\n  throw new Error(text);\n}","preventionTips":["Retry confidently: WriteFileAtomic never leaves a partial model_catalogs.json, so the same DELETE is idempotent","Keep the config home directory writable and off read-only mounts","On Windows, exclude the config dir from AV real-time scanning to avoid rename locks"],"tags":["filesystem","disk-full","atomic-write","model-catalog","go"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}