sipeed/picoclaw · error
Failed to save launcher config: %v
Error message
Failed to save launcher config: %v
What it means
Returned by PUT /api/system/launcher-config when launcherconfig.Save (launcherconfig/config.go:145-161) fails to persist the (already validated) config. Failure points: MkdirAll on the config directory, MarshalIndent (practically never for this struct), or os.WriteFile with mode 0600 - permission denied, disk full, or a read-only filesystem. Save re-validates first, but since the handler validated immediately before, an error here is almost always filesystem I/O.
Source
Thrown at web/backend/api/launcher_config.go:97
if err != nil {
http.Error(w, fmt.Sprintf("Failed to load launcher config: %v", err), http.StatusInternalServerError)
return
}
cfg.Port = payload.Port
cfg.Public = payload.Public
cfg.AllowedCIDRs = append([]string(nil), payload.AllowedCIDRs...)
if payload.AllowLocalhostBypass != nil {
cfg.AllowLocalhostBypass = *payload.AllowLocalhostBypass
}
cfg.TrustedProxyCIDRs = append([]string(nil), payload.TrustedProxyCIDRs...)
cfg.LegacyLauncherToken = ""
if err := launcherconfig.Validate(cfg); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := launcherconfig.Save(h.launcherConfigPath(), cfg); err != nil {
http.Error(w, fmt.Sprintf("Failed to save launcher config: %v", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(launcherConfigPayload{
Port: cfg.Port,
Public: cfg.Public,
AllowedCIDRs: append([]string(nil), cfg.AllowedCIDRs...),
AllowLocalhostBypass: cfg.AllowLocalhostBypass,
TrustedProxyCIDRs: append([]string(nil), cfg.TrustedProxyCIDRs...),
})
}
View on GitHub (pinned to 49183d7e8d)
Solutions
- Check the wrapped error - 'failed to create directory' vs a plain write error points at different paths
- Free disk space if the volume is full
- Make the config directory writable by the backend user (ownership plus mode), and confirm the mount is not read-only
- Retry the PUT after fixing the filesystem - the failed write left the previous config intact
Defensive patterns
Strategy: retry
Try / catch
for (let attempt = 0; attempt < 2; attempt++) {
const res = await fetch('/api/system/launcher-config', {method: 'PUT', ...});
if (res.ok) return res.json();
const text = await res.text();
if (text.startsWith('Failed to save launcher config')) {
if (text.includes('read-only') || text.includes('permission denied')) throw new Error('config dir not writable by backend - fix ownership/mount'); // not retryable
await sleep(1000); // transient (disk pressure): safe to retry, previous file untouched
continue;
}
throw new Error(text);
} Prevention
- Verify the config directory is writable by the service user before deploying
- Monitor disk space - WriteFileAtomic needs room for the temp file plus fsync
- Atomic write semantics make retries safe: a failed save never corrupts the previous config
When it happens
Trigger: Config directory on a read-only mount (container overlay, k8s ConfigMount); disk full; launcher-config.json owned by another user after a migration; SELinux/AppArmor denying writes to the path.
Common situations: Containers started with a read-only volume for config; disk exhaustion on small edge devices; files created by root during setup then served by an unprivileged service.
Related errors
- create output dir: %w
- write result: %w
- failed to save config: %w
- ✗ failed to create skills directory: %w
- failed to create media temp dir: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/2a1a09d5272d3506.
Report an issue: GitHub.