sipeed/picoclaw · error
Failed to save config: %v
Error message
Failed to save config: %v
What it means
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.
Source
Thrown at web/backend/api/models.go:348
http.Error(w, fmt.Sprintf("Validation error: %v", err), http.StatusBadRequest)
return
}
if mc.APIKey != "" {
mc.ModelConfig.SetAPIKey(mc.APIKey)
}
cfg, err := config.LoadConfig(h.configPath)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to load config: %v", err), http.StatusInternalServerError)
return
}
cfg.ModelList = append(cfg.ModelList, &mc.ModelConfig)
normalizeStoredModelProviders(cfg)
if err := config.SaveConfig(h.configPath, cfg); err != nil {
http.Error(w, fmt.Sprintf("Failed to save config: %v", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{
"status": "ok",
"index": len(cfg.ModelList) - 1,
})
}
// handleUpdateModel replaces a model configuration entry at the given index.
// If the request body omits api_key (or sends an empty string), the existing
// stored key is preserved so callers can update only api_base / proxy without
// exposing or clearing the secret.
//
// PUT /api/models/{index}
func (h *Handler) handleUpdateModel(w http.ResponseWriter, r *http.Request) {
idx, err := strconv.Atoi(r.PathValue("index"))View on GitHub (pinned to 49183d7e8d)
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
Example fix
# before: backend user cannot write the config dir $ ls -ld ~/.config/myapp drwxr-xr-x 2 root root 4096 ... /home/user/.config/myapp # after: give the running user ownership $ sudo chown -R $(whoami) ~/.config/myapp
Defensive patterns
Strategy: try-catch
Validate before calling
// before adding, confirm the config service is writable via a health probe // e.g. a HEAD/OPTIONS on /api/models plus one no-op update in staging
Try / catch
const res = await fetch('/api/models', { method: 'POST', body });
if (res.status === 500 && (await res.text()).includes('Failed to save config')) {
// do NOT resend blindly: report the cause (perms/disk/concurrent write) to the operator
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Failed to load config: %v
- create request: %w
- failed to save config: %w
- build request: %w
- failed to read security config: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/fe00a4654d2bbd14.
Report an issue: GitHub.