{"record":{"id":"3aff70341f5a62d5","repo":"sipeed/picoclaw","slug":"failed-to-create-skills-directory-w","errorCode":null,"errorMessage":"✗ failed to create skills directory: %w","messagePattern":"✗ failed to create skills directory: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/picoclaw/internal/skills/helpers.go","lineNumber":82,"sourceCode":"\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\n\tif result.IsMalwareBlocked {\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\n\t\treturn fmt.Errorf(\"\\u2717 Skill '%s' is flagged as malicious and cannot be installed.\\n\", target)","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/cmd/picoclaw/internal/skills/helpers.go#L64-L100","documentation":"os.MkdirAll failed to create <workspace>/skills before the download begins. The wrapped error explains why — typically the workspace path is not writable, something occupies the 'skills' path as a non-directory, or the filesystem is read-only.","triggerScenarios":"The workspace directory exists but the current user cannot write it, <workspace>/skills is a regular file, or the workspace sits on a read-only mount (container, locked volume) — helpers.go:82.","commonSituations":"Workspace owned by root after running picoclaw once with sudo, a read-only bind mount in a container, or a stale file named 'skills' created by an earlier tool.","solutions":["Check permissions: ls -ld <workspace> <workspace>/skills and fix with chown/chmod so your user can write.","Remove or rename anything occupying the 'skills' path that is not a directory.","If the mount is read-only, point the workspace at a writable location.","Retry the install after the fix."],"exampleFix":"# before: <workspace>/skills is a regular file\nrm \"<workspace>/skills\"\n# after\npicoclaw skills install clawhub/myskill","handlingStrategy":"validation","validationCode":"dir := filepath.Join(workspace, \"skills\")\nif info, err := os.Stat(dir); err == nil && !info.IsDir() {\n    return fmt.Errorf(\"%s exists and is not a directory\", dir)\n}\nif err := os.MkdirAll(dir, 0o755); err != nil {\n    return fmt.Errorf(\"cannot create %s: %w\", dir, err)\n}","typeGuard":"func skillsDirWritable(workspace string) bool {\n    return os.MkdirAll(filepath.Join(workspace, \"skills\"), 0o755) == nil\n}","tryCatchPattern":null,"preventionTips":["Pre-flight write checks in install scripts.","Never run the CLI under a different user than owns the workspace.","Keep the workspace on a writable volume in containers."],"tags":["filesystem","permissions","install","go"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}