{"record":{"id":"3cf5795613b80d7b","repo":"sipeed/picoclaw","slug":"failed-to-save-config-w","errorCode":null,"errorMessage":"failed to save config: %w","messagePattern":"failed to save config: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/picoclaw/internal/auth/wecom.go","lineNumber":130,"sourceCode":"\t}\n\n\tcfg, err := internal.LoadConfig()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to load config: %w\", err)\n\t}\n\n\topts := defaultWeComQRFlowOptions(timeout)\n\topts.Writer = writer\n\n\tbotInfo, err := scanner(ctx, opts)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tapplyWeComAuthResult(cfg, botInfo)\n\n\tif saveErr := config.SaveConfig(internal.GetConfigPath(), cfg); saveErr != nil {\n\t\treturn fmt.Errorf(\"failed to save config: %w\", saveErr)\n\t}\n\n\tfmt.Fprintln(writer)\n\tfmt.Fprintln(writer, \"WeCom connected.\")\n\tfmt.Fprintf(writer, \"Bot ID: %s\\n\", botInfo.BotID)\n\tfmt.Fprintf(writer, \"Config: %s\\n\", internal.GetConfigPath())\n\n\treturn nil\n}\n\nfunc defaultWeComQRFlowOptions(timeout time.Duration) wecomQRFlowOptions {\n\tif timeout <= 0 {\n\t\ttimeout = wecomQRPollTimeout\n\t}\n\n\treturn wecomQRFlowOptions{\n\t\tHTTPClient:    &http.Client{Timeout: wecomQRHTTPTimeout},\n\t\tGenerateURL:   wecomQRGenerateEndpoint,","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/cmd/picoclaw/internal/auth/wecom.go#L112-L148","documentation":"config.SaveConfig failed after a successful WeCom QR login, meaning the bot credentials (BotID + encrypted Secret) were obtained but NOT persisted. SaveConfig first writes a .security.yml alongside the config, then marshals and atomically writes config.json (pkg/config/config.go:1672-1700), so failure can come from either file: marshal error, unwritable directory, or atomic rename failure.","triggerScenarios":"~/.picoclaw directory or config.json owned by another user/root; disk full; PICOCLAW_CONFIG_PATH set to a read-only location; .security.yml locked by a concurrently running picoclaw process; invalid config state that cannot marshal.","commonSituations":"Running the login under sudo previously so files are root-owned; disk quota exhausted; two picoclaw instances writing config simultaneously; SELinux/AppArmor denying writes to the home dot-directory.","solutions":["Fix ownership of the config directory: chown -R $(whoami) ~/.picoclaw (or the PICOCLAW_CONFIG_PATH directory)","Check disk space (df -h) and inodes (df -i)","Close other running picoclaw processes that may hold the config, then re-run login","Verify PICOCLAW_CONFIG_PATH/PICOCLAW_HOME point to a writable location","Re-run the WeCom login after fixing writes — the credentials are only persisted on a successful save, so the bot must be re-linked"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"// Pre-flight writability check before an interactive login\np := internal.GetConfigPath()\nf, err := os.OpenFile(p, os.O_WRONLY|os.O_CREATE, 0o600)\nif err != nil {\n\treturn fmt.Errorf(\"config %s is not writable: %w\", p, err)\n}\nf.Close()","typeGuard":"func isPermissionErr(err error) bool {\n\treturn err != nil && (os.IsPermission(err) || errors.Is(err, fs.ErrPermission))\n}","tryCatchPattern":"if saveErr := config.SaveConfig(internal.GetConfigPath(), cfg); saveErr != nil {\n\tif isPermissionErr(saveErr) {\n\t\tfmt.Fprintf(writer, \"Login succeeded but config could not be saved. Fix permissions on %s and re-run.\\nBot ID: %s\\n\", internal.GetConfigPath(), botInfo.BotID)\n\t}\n\treturn fmt.Errorf(\"failed to save config: %w\", saveErr)\n}","preventionTips":["Ensure ~/.picoclaw (and PICOCLAW_CONFIG_PATH target) is user-writable before login flows","Run only one picoclaw instance when credentials may be written","Monitor disk space; atomic writes fail on full disks"],"tags":["go","config","filesystem","permissions","wecom"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}