sipeed/picoclaw · error
credential: keygen: write private key %q: %w
Error message
credential: keygen: write private key %q: %w
What it means
GenerateSSHKey writes the OpenSSH PEM private key with os.WriteFile(path, privPEM, 0600) and wraps failures. Common causes: permission denied on the directory, disk full (ENOSPC/EDQUOT), a directory existing at the key path, or an immutable existing file. Because the private key is written before the .pub file, a failure here leaves nothing behind (the pub write at line 58 never happens).
Source
Thrown at pkg/credential/keygen.go:46
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)
}
// Marshal public key as authorized_keys line.
sshPub, err := ssh.NewPublicKey(pubRaw)
if err != nil {
return fmt.Errorf("credential: keygen: marshal public key: %w", err)
}
pubLine := ssh.MarshalAuthorizedKey(sshPub)
pubPath := path + ".pub"
if err := os.WriteFile(pubPath, pubLine, 0o644); err != nil {
return fmt.Errorf("credential: keygen: write public key %q: %w", pubPath, err)
}
return nil
}
View on GitHub (pinned to 49183d7e8d)
Solutions
- Check writability and space: `ls -ld <dir>`, `df -h <dir>`, then free space or fix ownership
- Ensure no directory occupies the key path: `ls -ld <path>`
- If an immutable/locked file exists, remove it first (`chattr -i` on Linux if applicable)
- Re-run the keygen after fixing the filesystem issue - the function overwrites existing files cleanly
Example fix
// before
if err := credential.GenerateSSHKey(path); err != nil {
return err
}
// after: pre-flight the exact failure modes
if fi, err := os.Stat(path); err == nil && fi.IsDir() {
return fmt.Errorf("%s is a directory", path)
}
dir := filepath.Dir(path)
if err := unix.Access(dir, unix.W_OK); err != nil {
return fmt.Errorf("no write access to %s: %w", dir, err)
}
if err := credential.GenerateSSHKey(path); err != nil {
return err
} Defensive patterns
Strategy: validation
Validate before calling
func keyPathWritable(path string) error {
if fi, err := os.Stat(path); err == nil && fi.IsDir() {
return fmt.Errorf("%s is a directory", path)
}
dir := filepath.Dir(path)
f, err := os.OpenFile(filepath.Join(dir, ".keygen-probe"), os.O_CREATE|os.O_WRONLY, 0o600)
if err != nil {
return fmt.Errorf("directory %s not writable: %w", dir, err)
}
f.Close()
os.Remove(filepath.Join(dir, ".keygen-probe"))
return nil
} Prevention
- Pre-check directory ownership/mode (0700, daemon uid) before keygen
- Monitor disk space on the key filesystem; ENOSPC is the top cause
- Provision the parent directory in config management instead of improvising at runtime
When it happens
Trigger: GenerateSSHKey where the parent dir is not writable by the current user (dir was created 0700 by another uid); /tmp full in ephemeral CI; path is an existing directory; previous key file has the immutable bit set.
Common situations: Keygen as root after dir creation as user (or vice versa); small disks / quota-limited home directories; containers with size-limited tmpfs; leftover directory named picoclaw_ed25519.key from a failed run.
Related errors
- credential: keygen: cannot create directory %q: %w
- credential: keygen: write public key %q: %w
- Failed to save launcher config: %v
- create output dir: %w
- write result: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/eb3bba3289d060f1.
Report an issue: GitHub.