sipeed/picoclaw · warning

✗ skill '%s' already installed at %s

Error message

✗ skill '%s' already installed at %s

What it means

Install was refused because <workspace>/skills/<dirName> already exists — os.Stat succeeded. This is an idempotency guard that fires before download; nothing is modified and the existing install stays intact.

Source

Thrown at cmd/picoclaw/internal/skills/helpers.go:75

	registryMgr := skills.NewRegistryManagerFromToolsConfig(cfg.Tools.Skills)

	registry := registryMgr.GetRegistry(registryName)
	if registry == nil {
		return fmt.Errorf("✗  registry '%s' not found or not enabled. check your config.json.", registryName)
	}

	dirName, err := registry.ResolveInstallDirName(target)
	if err != nil {
		return fmt.Errorf("✗  invalid install target %q: %w", target, err)
	}

	fmt.Printf("Installing skill '%s' from %s registry...\n", target, registryName)

	workspace := cfg.WorkspacePath()
	targetDir := filepath.Join(workspace, "skills", dirName)

	if _, err = os.Stat(targetDir); err == nil {
		return fmt.Errorf("\u2717 skill '%s' already installed at %s", dirName, targetDir)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
	defer cancel()

	if err = os.MkdirAll(filepath.Join(workspace, "skills"), 0o755); err != nil {
		return fmt.Errorf("\u2717 failed to create skills directory: %w", err)
	}

	result, err := registry.DownloadAndInstall(ctx, target, "", targetDir)
	if err != nil {
		rmErr := os.RemoveAll(targetDir)
		if rmErr != nil {
			fmt.Printf("\u2717 Failed to remove partial install: %v\n", rmErr)
		}
		return fmt.Errorf("✗ failed to install skill: %w", err)
	}

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Keep the existing install if it is already what you wanted.
  2. Otherwise remove it first: `picoclaw skills remove <name>`, then reinstall.
  3. If the directory is a corrupt leftover, delete <workspace>/skills/<dirName> manually and reinstall.
  4. Check `picoclaw skills list` to confirm what is installed before retrying.

Example fix

# before
picoclaw skills install clawhub/myskill   # dir already exists
# after
picoclaw skills remove myskill && picoclaw skills install clawhub/myskill
Defensive patterns

Strategy: validation

Validate before calling

targetDir := filepath.Join(workspace, "skills", dirName)
if _, err := os.Stat(targetDir); err == nil {
    return nil // already installed — treat as success (idempotent) or prompt
}

Type guard

func skillInstalled(workspace, dirName string) bool {
    _, err := os.Stat(filepath.Join(workspace, "skills", dirName))
    return err == nil
}

Prevention

When it happens

Trigger: Re-running `skills install` for a skill whose resolved directory already exists under the workspace's skills folder (helpers.go:75), regardless of which registry or version installed it.

Common situations: Re-running an install after an interrupted session, two registries resolving to the same dirName, or a leftover directory from a manual copy.

Related errors


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