sipeed/picoclaw · error

failed to save config: %w

Error message

failed to save config: %w

What it means

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.

Source

Thrown at cmd/picoclaw/internal/auth/wecom.go:130

	}

	cfg, err := internal.LoadConfig()
	if err != nil {
		return fmt.Errorf("failed to load config: %w", err)
	}

	opts := defaultWeComQRFlowOptions(timeout)
	opts.Writer = writer

	botInfo, err := scanner(ctx, opts)
	if err != nil {
		return err
	}

	applyWeComAuthResult(cfg, botInfo)

	if saveErr := config.SaveConfig(internal.GetConfigPath(), cfg); saveErr != nil {
		return fmt.Errorf("failed to save config: %w", saveErr)
	}

	fmt.Fprintln(writer)
	fmt.Fprintln(writer, "WeCom connected.")
	fmt.Fprintf(writer, "Bot ID: %s\n", botInfo.BotID)
	fmt.Fprintf(writer, "Config: %s\n", internal.GetConfigPath())

	return nil
}

func defaultWeComQRFlowOptions(timeout time.Duration) wecomQRFlowOptions {
	if timeout <= 0 {
		timeout = wecomQRPollTimeout
	}

	return wecomQRFlowOptions{
		HTTPClient:    &http.Client{Timeout: wecomQRHTTPTimeout},
		GenerateURL:   wecomQRGenerateEndpoint,

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Fix ownership of the config directory: chown -R $(whoami) ~/.picoclaw (or the PICOCLAW_CONFIG_PATH directory)
  2. Check disk space (df -h) and inodes (df -i)
  3. Close other running picoclaw processes that may hold the config, then re-run login
  4. Verify PICOCLAW_CONFIG_PATH/PICOCLAW_HOME point to a writable location
  5. Re-run the WeCom login after fixing writes — the credentials are only persisted on a successful save, so the bot must be re-linked
Defensive patterns

Strategy: fallback

Validate before calling

// Pre-flight writability check before an interactive login
p := internal.GetConfigPath()
f, err := os.OpenFile(p, os.O_WRONLY|os.O_CREATE, 0o600)
if err != nil {
	return fmt.Errorf("config %s is not writable: %w", p, err)
}
f.Close()

Type guard

func isPermissionErr(err error) bool {
	return err != nil && (os.IsPermission(err) || errors.Is(err, fs.ErrPermission))
}

Try / catch

if saveErr := config.SaveConfig(internal.GetConfigPath(), cfg); saveErr != nil {
	if isPermissionErr(saveErr) {
		fmt.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)
	}
	return fmt.Errorf("failed to save config: %w", saveErr)
}

Prevention

When it happens

Trigger: ~/.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.

Common situations: 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.

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/3cf5795613b80d7b. Report an issue: GitHub.