{"record":{"id":"6cb099531ccc982b","repo":"sipeed/picoclaw","slug":"skill-s-already-installed-at-s","errorCode":null,"errorMessage":"✗ skill '%s' already installed at %s","messagePattern":"✗ skill '(.+?)' already installed at (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"cmd/picoclaw/internal/skills/helpers.go","lineNumber":75,"sourceCode":"\tregistryMgr := skills.NewRegistryManagerFromToolsConfig(cfg.Tools.Skills)\n\n\tregistry := registryMgr.GetRegistry(registryName)\n\tif registry == nil {\n\t\treturn fmt.Errorf(\"✗  registry '%s' not found or not enabled. check your config.json.\", registryName)\n\t}\n\n\tdirName, err := registry.ResolveInstallDirName(target)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"✗  invalid install target %q: %w\", target, err)\n\t}\n\n\tfmt.Printf(\"Installing skill '%s' from %s registry...\\n\", target, registryName)\n\n\tworkspace := cfg.WorkspacePath()\n\ttargetDir := filepath.Join(workspace, \"skills\", dirName)\n\n\tif _, err = os.Stat(targetDir); err == nil {\n\t\treturn fmt.Errorf(\"\\u2717 skill '%s' already installed at %s\", dirName, targetDir)\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)\n\tdefer cancel()\n\n\tif err = os.MkdirAll(filepath.Join(workspace, \"skills\"), 0o755); err != nil {\n\t\treturn fmt.Errorf(\"\\u2717 failed to create skills directory: %w\", err)\n\t}\n\n\tresult, err := registry.DownloadAndInstall(ctx, target, \"\", targetDir)\n\tif err != nil {\n\t\trmErr := os.RemoveAll(targetDir)\n\t\tif rmErr != nil {\n\t\t\tfmt.Printf(\"\\u2717 Failed to remove partial install: %v\\n\", rmErr)\n\t\t}\n\t\treturn fmt.Errorf(\"✗ failed to install skill: %w\", err)\n\t}\n","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/cmd/picoclaw/internal/skills/helpers.go#L57-L93","documentation":"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.","triggerScenarios":"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.","commonSituations":"Re-running an install after an interrupted session, two registries resolving to the same dirName, or a leftover directory from a manual copy.","solutions":["Keep the existing install if it is already what you wanted.","Otherwise remove it first: `picoclaw skills remove <name>`, then reinstall.","If the directory is a corrupt leftover, delete <workspace>/skills/<dirName> manually and reinstall.","Check `picoclaw skills list` to confirm what is installed before retrying."],"exampleFix":"# before\npicoclaw skills install clawhub/myskill   # dir already exists\n# after\npicoclaw skills remove myskill && picoclaw skills install clawhub/myskill","handlingStrategy":"validation","validationCode":"targetDir := filepath.Join(workspace, \"skills\", dirName)\nif _, err := os.Stat(targetDir); err == nil {\n    return nil // already installed — treat as success (idempotent) or prompt\n}","typeGuard":"func skillInstalled(workspace, dirName string) bool {\n    _, err := os.Stat(filepath.Join(workspace, \"skills\", dirName))\n    return err == nil\n}","tryCatchPattern":null,"preventionTips":["Run `skills list` before scripted installs.","Make install scripts idempotent by checking for the directory first.","Track installed names in one place so reinstall flows remove-then-install deliberately."],"tags":["install","filesystem","idempotency","cli","go"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}