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

  1. Check permissions: ls -ld <workspace> <workspace>/skills and fix with chown/chmod so your user can write.
  2. Remove or rename anything occupying the 'skills' path that is not a directory.
  3. If the mount is read-only, point the workspace at a writable location.
  4. 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

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


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