sipeed/picoclaw · error

credential: keygen: cannot create directory %q: %w

Error message

credential: keygen: cannot create directory %q: %w

What it means

GenerateSSHKey creates the parent directory of the requested key path (normally ~/.ssh) with os.MkdirAll(..., 0700) before writing keys, and wraps any failure here. Typical causes: permission denied on an existing parent, a regular file occupying the directory name, or a read-only filesystem. The failing directory is included in the message.

Source

Thrown at pkg/credential/keygen.go:30

)

// DefaultSSHKeyPath returns the canonical path for the picoclaw-specific SSH key.
// The path is always ~/.ssh/picoclaw_ed25519.key (os.UserHomeDir is cross-platform).
func DefaultSSHKeyPath() (string, error) {
	home, err := os.UserHomeDir()
	if err != nil {
		return "", fmt.Errorf("credential: cannot determine home directory: %w", err)
	}
	return filepath.Join(home, ".ssh", "picoclaw_ed25519.key"), nil
}

// GenerateSSHKey generates an Ed25519 SSH key pair and writes the private key
// to path (permissions 0600) and the public key to path+".pub" (permissions 0644).
// The ~/.ssh/ directory is created with 0700 if it does not exist.
// If the files already exist they are overwritten.
func GenerateSSHKey(path string) error {
	if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
		return fmt.Errorf("credential: keygen: cannot create directory %q: %w", filepath.Dir(path), err)
	}

	pubRaw, privRaw, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		return fmt.Errorf("credential: keygen: ed25519 key generation failed: %w", err)
	}

	// Marshal private key as OpenSSH PEM.
	block, err := ssh.MarshalPrivateKey(privRaw, "")
	if err != nil {
		return fmt.Errorf("credential: keygen: marshal private key: %w", err)
	}
	privPEM := pem.EncodeToMemory(block)

	if err = os.WriteFile(path, privPEM, 0o600); err != nil {
		return fmt.Errorf("credential: keygen: write private key %q: %w", path, err)
	}

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Create the directory manually with the right ownership: `install -d -m 700 -o <user> <dir>`
  2. Check nothing occupies the path: `ls -ld <dir>` - if it is a file, remove or rename it
  3. If the filesystem is read-only, choose a key path under a writable volume and point PICOCLAW_SSH_KEY_PATH at it
  4. Run the keygen as the user who will own the key

Example fix

# before: keygen as root into a user home that is not writable
sudo picoclaw keygen  # -> cannot create directory "/home/alice/.ssh"

# after: pre-create with correct ownership, then keygen as that user
sudo install -d -m 700 -o alice -g alice /home/alice/.ssh
sudo -u alice picoclaw keygen
Defensive patterns

Strategy: validation

Validate before calling

func keyDirReady(path string) error {
    dir := filepath.Dir(path)
    if fi, err := os.Stat(dir); err == nil {
        if !fi.IsDir() {
            return fmt.Errorf("%s exists and is not a directory", dir)
        }
        return nil
    }
    return os.MkdirAll(dir, 0o700)
}

Prevention

When it happens

Trigger: GenerateSSHKey(path) where filepath.Dir(path) cannot be created: path like /home/alice/.ssh/picoclaw.key while running as a user without write access to /home/alice; a file named .ssh already exists; target directory on a read-only mount (immutable container layer); path with an existing parent owned by root with 0755 when running non-root.

Common situations: Keygen run as root for a user's home that has restrictive perms or is NFS-mounted root-squashed; container images with read-only rootfs and no writable volume for keys; leftover file where a directory belongs.

Related errors


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