{"record":{"id":"fe00a4654d2bbd14","repo":"sipeed/picoclaw","slug":"failed-to-save-config-v-fe00a4","errorCode":null,"errorMessage":"Failed to save config: %v","messagePattern":"Failed to save config: (.+?)","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"web/backend/api/models.go","lineNumber":348,"sourceCode":"\t\thttp.Error(w, fmt.Sprintf(\"Validation error: %v\", err), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tif mc.APIKey != \"\" {\n\t\tmc.ModelConfig.SetAPIKey(mc.APIKey)\n\t}\n\n\tcfg, err := config.LoadConfig(h.configPath)\n\tif err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"Failed to load config: %v\", err), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tcfg.ModelList = append(cfg.ModelList, &mc.ModelConfig)\n\tnormalizeStoredModelProviders(cfg)\n\n\tif err := config.SaveConfig(h.configPath, cfg); err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"Failed to save config: %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]any{\n\t\t\"status\": \"ok\",\n\t\t\"index\":  len(cfg.ModelList) - 1,\n\t})\n}\n\n// handleUpdateModel replaces a model configuration entry at the given index.\n// If the request body omits api_key (or sends an empty string), the existing\n// stored key is preserved so callers can update only api_base / proxy without\n// exposing or clearing the secret.\n//\n//\tPUT /api/models/{index}\nfunc (h *Handler) handleUpdateModel(w http.ResponseWriter, r *http.Request) {\n\tidx, err := strconv.Atoi(r.PathValue(\"index\"))","sourceCodeStart":330,"sourceCodeEnd":366,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/web/backend/api/models.go#L330-L366","documentation":"HTTP 500 returned by POST /api/models (handleAddModel) when config.SaveConfig(h.configPath, cfg) fails while persisting the appended entry. SaveConfig first writes the companion .security.yml (failure returns immediately), then marshals and atomically writes config.json via fileutil.WriteFileAtomic. Typical causes: read-only or wrong-owner config dir, disk full, or a concurrent writer racing the atomic replace.","triggerScenarios":"POST /api/models succeeds in memory but the write fails: ENOSPC on a full disk, EACCES on ~/.config/<app> not writable by the service user, a second process holding/locking the file, or saveSecurityConfig failing to write .security.yml.","commonSituations":"Running the backend under a systemd/nginx user that lacks write access to the config dir; small root partitions or quotas; two UI tabs (or CLI + web UI) saving config at the same moment; antivirus/backup tools briefly locking the config path on some platforms.","solutions":["Check the backend log — SaveConfig logs details of the .security.yml or WriteFileAtomic failure","Ensure the config directory is writable by the service user: chmod u+w <config dir>, chown appropriately","Free disk space / raise quota if ENOSPC","Close competing config writers (other tabs, CLI sessions) and retry the POST once"],"exampleFix":"# before: backend user cannot write the config dir\n$ ls -ld ~/.config/myapp\ndrwxr-xr-x 2 root root 4096 ... /home/user/.config/myapp\n\n# after: give the running user ownership\n$ sudo chown -R $(whoami) ~/.config/myapp","handlingStrategy":"try-catch","validationCode":"// before adding, confirm the config service is writable via a health probe\n// e.g. a HEAD/OPTIONS on /api/models plus one no-op update in staging","typeGuard":null,"tryCatchPattern":"const res = await fetch('/api/models', { method: 'POST', body });\nif (res.status === 500 && (await res.text()).includes('Failed to save config')) {\n  // do NOT resend blindly: report the cause (perms/disk/concurrent write) to the operator\n}","preventionTips":["Give the backend user write access to the config directory and .security.yml","Monitor disk space where config.json lives","Use one config editor at a time; concurrent saves race the atomic replace","Test write permissions after any service-user or deployment change"],"tags":["go","http","config","filesystem","internal-server-error"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}