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
- Create the directory manually with the right ownership: `install -d -m 700 -o <user> <dir>`
- Check nothing occupies the path: `ls -ld <dir>` - if it is a file, remove or rename it
- If the filesystem is read-only, choose a key path under a writable volume and point PICOCLAW_SSH_KEY_PATH at it
- 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
- Pre-create ~/.ssh with 0700 during provisioning rather than relying on keygen
- Run keygen as the user who will own the key
- On read-only filesystems, place keys under a writable volume and set PICOCLAW_SSH_KEY_PATH
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
- credential: cannot read SSH key %q: %w
- credential: keygen: write private key %q: %w
- credential: keygen: write public key %q: %w
- create output dir: %w
- failed to save config: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/1731944790a9d987.
Report an issue: GitHub.