{"record":{"id":"d7ad4b6dd101deea","repo":"sipeed/picoclaw","slug":"skill-name-is-required","errorCode":null,"errorMessage":"skill name is required","messagePattern":"skill name is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"cmd/picoclaw/internal/skills/helpers.go","lineNumber":160,"sourceCode":"\nfunc workspaceHasValidSkillDirectory(workspace, directory string) bool {\n\tloader := skills.NewSkillsLoader(workspace, \"\", \"\")\n\tfor _, skill := range loader.ListSkills() {\n\t\tif skill.Source != \"workspace\" {\n\t\t\tcontinue\n\t\t}\n\t\tif filepath.Base(filepath.Dir(skill.Path)) == directory {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc skillsRemoveFromWorkspace(workspace string, toolsConfig config.SkillsToolsConfig, skillName string) error {\n\tname := strings.TrimSpace(skillName)\n\tname = strings.Trim(name, \"/\")\n\tif name == \"\" {\n\t\treturn fmt.Errorf(\"skill name is required\")\n\t}\n\tif strings.Contains(name, \"/\") {\n\t\tdirName, err := skills.GitHubInstallDirNameFromToolsConfig(toolsConfig, name)\n\t\tif err != nil || dirName == \"\" {\n\t\t\treturn fmt.Errorf(\"invalid skill name %q\", skillName)\n\t\t}\n\t\tname = dirName\n\t}\n\tif name == \".\" || name == \"..\" {\n\t\treturn fmt.Errorf(\"invalid skill name %q\", skillName)\n\t}\n\tskillDir := filepath.Join(workspace, \"skills\", name)\n\tif _, err := os.Stat(skillDir); os.IsNotExist(err) {\n\t\treturn fmt.Errorf(\"skill '%s' not found\", name)\n\t}\n\tif err := os.RemoveAll(skillDir); err != nil {\n\t\treturn fmt.Errorf(\"failed to remove skill '%s': %w\", name, err)\n\t}","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/cmd/picoclaw/internal/skills/helpers.go#L142-L178","documentation":"skillsRemoveFromWorkspace requires a skill name; after strings.TrimSpace and stripping surrounding '/', the argument was empty. Pure argument validation that fires before any filesystem access, so nothing is deleted.","triggerScenarios":"Running `skills remove` with no argument, an empty string, \"   \", or \"/\" — name is empty after the trims at helpers.go:160.","commonSituations":"Forgetting the argument, a shell variable that expands to empty ($SKILL unset), or a script looping over an empty list.","solutions":["Pass the skill name: `picoclaw skills remove <name>`.","List installed skills first (`picoclaw skills list`) to get exact names.","In scripts, skip the call when the variable is empty or unset."],"exampleFix":"# before\npicoclaw skills remove \"$SKILL\"   # SKILL is unset -> \"\"\n# after\npicoclaw skills remove myskill","handlingStrategy":"validation","validationCode":"name := strings.Trim(strings.TrimSpace(skillName), \"/\")\nif name == \"\" {\n    return fmt.Errorf(\"skill name is required\")\n}","typeGuard":"func hasSkillName(s string) bool {\n    return strings.Trim(strings.TrimSpace(s), \"/\") != \"\"\n}","tryCatchPattern":null,"preventionTips":["Declare the name as a required cobra positional arg (Args: cobra.ExactArgs(1)) so the CLI rejects empties before dispatch.","Use : \"${SKILL:?unset}\" in shell scripts to fail on empty variables."],"tags":["validation","cli","go"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}