{"record":{"id":"2a1a09d5272d3506","repo":"sipeed/picoclaw","slug":"failed-to-save-launcher-config-v","errorCode":null,"errorMessage":"Failed to save launcher config: %v","messagePattern":"Failed to save launcher config: (.+?)","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"web/backend/api/launcher_config.go","lineNumber":97,"sourceCode":"\tif err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"Failed to load launcher config: %v\", err), http.StatusInternalServerError)\n\t\treturn\n\t}\n\tcfg.Port = payload.Port\n\tcfg.Public = payload.Public\n\tcfg.AllowedCIDRs = append([]string(nil), payload.AllowedCIDRs...)\n\tif payload.AllowLocalhostBypass != nil {\n\t\tcfg.AllowLocalhostBypass = *payload.AllowLocalhostBypass\n\t}\n\tcfg.TrustedProxyCIDRs = append([]string(nil), payload.TrustedProxyCIDRs...)\n\tcfg.LegacyLauncherToken = \"\"\n\tif err := launcherconfig.Validate(cfg); err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tif err := launcherconfig.Save(h.launcherConfigPath(), cfg); err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"Failed to save launcher config: %v\", err), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tjson.NewEncoder(w).Encode(launcherConfigPayload{\n\t\tPort:                 cfg.Port,\n\t\tPublic:               cfg.Public,\n\t\tAllowedCIDRs:         append([]string(nil), cfg.AllowedCIDRs...),\n\t\tAllowLocalhostBypass: cfg.AllowLocalhostBypass,\n\t\tTrustedProxyCIDRs:    append([]string(nil), cfg.TrustedProxyCIDRs...),\n\t})\n}\n","sourceCodeStart":79,"sourceCodeEnd":110,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/web/backend/api/launcher_config.go#L79-L110","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"for (let attempt = 0; attempt < 2; attempt++) {\n  const res = await fetch('/api/system/launcher-config', {method: 'PUT', ...});\n  if (res.ok) return res.json();\n  const text = await res.text();\n  if (text.startsWith('Failed to save launcher config')) {\n    if (text.includes('read-only') || text.includes('permission denied')) throw new Error('config dir not writable by backend - fix ownership/mount'); // not retryable\n    await sleep(1000);   // transient (disk pressure): safe to retry, previous file untouched\n    continue;\n  }\n  throw new Error(text);\n}","preventionTips":["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"],"tags":["filesystem","disk-full","permissions","launcher","go"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}