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

  1. Run `picoclaw skills list` and pass the exact installed directory name.
  2. For owner/repo forms, make sure that GitHub skill is actually configured/installed before removing by that name.
  3. Never pass '.' or '..' — they are always rejected.
  4. 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

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


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