sipeed/picoclaw · error
invalid skill name %q
Error message
invalid skill name %q
What it means
The skill name failed remove-side validation: either it contains '/' but GitHubInstallDirNameFromToolsConfig could not resolve it to an installed directory, or it is the path-special '.' / '..' (rejected as a traversal guard before filepath.Join). Nothing is deleted.
Source
Thrown at cmd/picoclaw/internal/skills/helpers.go:165
continue
}
if filepath.Base(filepath.Dir(skill.Path)) == directory {
return true
}
}
return false
}
func skillsRemoveFromWorkspace(workspace string, toolsConfig config.SkillsToolsConfig, skillName string) error {
name := strings.TrimSpace(skillName)
name = strings.Trim(name, "/")
if name == "" {
return fmt.Errorf("skill name is required")
}
if strings.Contains(name, "/") {
dirName, err := skills.GitHubInstallDirNameFromToolsConfig(toolsConfig, name)
if err != nil || dirName == "" {
return fmt.Errorf("invalid skill name %q", skillName)
}
name = dirName
}
if name == "." || name == ".." {
return fmt.Errorf("invalid skill name %q", skillName)
}
skillDir := filepath.Join(workspace, "skills", name)
if _, err := os.Stat(skillDir); os.IsNotExist(err) {
return fmt.Errorf("skill '%s' not found", name)
}
if err := os.RemoveAll(skillDir); err != nil {
return fmt.Errorf("failed to remove skill '%s': %w", name, err)
}
return nil
}
func skillsInstallBuiltinCmd(workspace string) {
builtinSkillsDir := "./picoclaw/skills"View on GitHub (pinned to 49183d7e8d)
Solutions
- Run `picoclaw skills list` and pass the exact installed directory name.
- For owner/repo forms, make sure that GitHub skill is actually configured/installed before removing by that name.
- Never pass '.' or '..' — they are always rejected.
- Reinstall under the intended name if you are unsure which dir name the install produced.
Example fix
# before picoclaw skills remove ../other # after picoclaw skills remove myskill
Defensive patterns
Strategy: validation
Validate before calling
name := strings.Trim(strings.TrimSpace(skillName), "/")
if name == "" || name == "." || name == ".." {
return fmt.Errorf("invalid skill name %q", skillName)
}
if strings.Contains(name, "/") {
dirName, err := skills.GitHubInstallDirNameFromToolsConfig(toolsConfig, name)
if err != nil || dirName == "" {
return fmt.Errorf("invalid skill name %q", skillName)
}
name = dirName
} Type guard
func removableSkillName(toolsConfig config.SkillsToolsConfig, s string) (string, bool) {
name := strings.Trim(strings.TrimSpace(s), "/")
if name == "" || name == "." || name == ".." {
return "", false
}
if strings.Contains(name, "/") {
d, err := skills.GitHubInstallDirNameFromToolsConfig(toolsConfig, name)
if err != nil || d == "" {
return "", false
}
return d, true
}
return name, true
} Prevention
- Source skill names from `skills list` output in scripts.
- Reject path-like inputs ('.', '..', embedded slashes) early in wrappers.
- Use the same name form for install and remove so directory mapping never diverges.
When it happens
Trigger: Removing "owner/nonexistent-repo" (slash form that resolves to nothing), a GitHub-style name whose owner/repo is absent from the tools config, or passing '.' / '..' — helpers.go:165 (slash branch) and helpers.go:169 (dot branch).
Common situations: Using the registry-style name when the skill was installed under a different directory name, typos in owner/repo, or probing the CLI with path-like inputs.
Related errors
- local command %q does not exist
- local command %q is a directory
- ✗ invalid registry name: %w
- ✗ invalid install target %q: %w
- ✗ skill '%s' already installed at %s
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/6593b1423fec117d.
Report an issue: GitHub.