sipeed/picoclaw · error
✗ failed to create skills directory: %w
Error message
✗ failed to create skills directory: %w
What it means
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.
Source
Thrown at cmd/picoclaw/internal/skills/helpers.go:82
dirName, err := registry.ResolveInstallDirName(target)
if err != nil {
return fmt.Errorf("✗ invalid install target %q: %w", target, err)
}
fmt.Printf("Installing skill '%s' from %s registry...\n", target, registryName)
workspace := cfg.WorkspacePath()
targetDir := filepath.Join(workspace, "skills", dirName)
if _, err = os.Stat(targetDir); err == nil {
return fmt.Errorf("\u2717 skill '%s' already installed at %s", dirName, targetDir)
}
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
if err = os.MkdirAll(filepath.Join(workspace, "skills"), 0o755); err != nil {
return fmt.Errorf("\u2717 failed to create skills directory: %w", err)
}
result, err := registry.DownloadAndInstall(ctx, target, "", targetDir)
if err != nil {
rmErr := os.RemoveAll(targetDir)
if rmErr != nil {
fmt.Printf("\u2717 Failed to remove partial install: %v\n", rmErr)
}
return fmt.Errorf("✗ failed to install skill: %w", err)
}
if result.IsMalwareBlocked {
rmErr := os.RemoveAll(targetDir)
if rmErr != nil {
fmt.Printf("\u2717 Failed to remove partial install: %v\n", rmErr)
}
return fmt.Errorf("\u2717 Skill '%s' is flagged as malicious and cannot be installed.\n", target)View on GitHub (pinned to 49183d7e8d)
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.
Example fix
# before: <workspace>/skills is a regular file rm "<workspace>/skills" # after picoclaw skills install clawhub/myskill
Defensive patterns
Strategy: validation
Validate before calling
dir := filepath.Join(workspace, "skills")
if info, err := os.Stat(dir); err == nil && !info.IsDir() {
return fmt.Errorf("%s exists and is not a directory", dir)
}
if err := os.MkdirAll(dir, 0o755); err != nil {
return fmt.Errorf("cannot create %s: %w", dir, err)
} Type guard
func skillsDirWritable(workspace string) bool {
return os.MkdirAll(filepath.Join(workspace, "skills"), 0o755) == nil
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- create output dir: %w
- failed to save config: %w
- ✗ skill '%s' already installed at %s
- ✗ failed to persist skill metadata: %w
- failed to create media temp dir: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/3aff70341f5a62d5.
Report an issue: GitHub.