{"record":{"id":"6593b1423fec117d","repo":"sipeed/picoclaw","slug":"invalid-skill-name-q","errorCode":null,"errorMessage":"invalid skill name %q","messagePattern":"invalid skill name %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/picoclaw/internal/skills/helpers.go","lineNumber":165,"sourceCode":"\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}\n\treturn nil\n}\n\nfunc skillsInstallBuiltinCmd(workspace string) {\n\tbuiltinSkillsDir := \"./picoclaw/skills\"","sourceCodeStart":147,"sourceCodeEnd":183,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/cmd/picoclaw/internal/skills/helpers.go#L147-L183","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"# before\npicoclaw skills remove ../other\n# after\npicoclaw skills remove myskill","handlingStrategy":"validation","validationCode":"name := strings.Trim(strings.TrimSpace(skillName), \"/\")\nif name == \"\" || name == \".\" || name == \"..\" {\n    return fmt.Errorf(\"invalid skill name %q\", skillName)\n}\nif strings.Contains(name, \"/\") {\n    dirName, err := skills.GitHubInstallDirNameFromToolsConfig(toolsConfig, name)\n    if err != nil || dirName == \"\" {\n        return fmt.Errorf(\"invalid skill name %q\", skillName)\n    }\n    name = dirName\n}","typeGuard":"func removableSkillName(toolsConfig config.SkillsToolsConfig, s string) (string, bool) {\n    name := strings.Trim(strings.TrimSpace(s), \"/\")\n    if name == \"\" || name == \".\" || name == \"..\" {\n        return \"\", false\n    }\n    if strings.Contains(name, \"/\") {\n        d, err := skills.GitHubInstallDirNameFromToolsConfig(toolsConfig, name)\n        if err != nil || d == \"\" {\n            return \"\", false\n        }\n        return d, true\n    }\n    return name, true\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["validation","cli","filesystem","path-traversal","go"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}