lima-vm/lima · error

could not create %#q directory: %w

Error message

could not create %#q directory: %w

What it means

DefaultPubKeys ensures $LIMA_HOME/_config exists before generating a default key pair; if os.MkdirAll of that directory fails the error is wrapped with the directory path. This usually indicates a filesystem-level problem, not a Lima logic error.

Source

Thrown at pkg/sshutil/sshutil.go:305

// DefaultPubKeys returns the public key from $LIMA_HOME/_config/user.pub.
// The key will be created if it does not yet exist.
//
// When loadDotSSH is true, ~/.ssh/*.pub will be appended to make the VM accessible without specifying
// an identity explicitly.
func DefaultPubKeys(ctx context.Context, loadDotSSH bool) ([]PubKey, error) {
	// Read $LIMA_HOME/_config/user.pub
	configDir, err := dirnames.LimaConfigDir()
	if err != nil {
		return nil, err
	}
	_, err = os.Stat(filepath.Join(configDir, filenames.UserPrivateKey))
	if err != nil {
		if !errors.Is(err, os.ErrNotExist) {
			return nil, err
		}
		if err := os.MkdirAll(configDir, 0o700); err != nil {
			return nil, fmt.Errorf("could not create %#q directory: %w", configDir, err)
		}
		if err := lockutil.WithDirLock(configDir, func() error {
			// no passphrase, no user@host comment
			privPath := filepath.Join(configDir, filenames.UserPrivateKey)
			keygenExe := "ssh-keygen"
			if runtime.GOOS == "windows" {
				sshExe, sshErr := NewSSHExe()
				if sshErr != nil {
					return sshErr
				}
				keygenExe = companionForSSH(sshExe, "ssh-keygen")
				privPath, err = PathForSSH(ctx, sshExe, privPath)
				if err != nil {
					return err
				}
			}
			keygenCmd := exec.CommandContext(ctx, keygenExe, "-t", "ed25519", "-q", "-N", "",
				"-C", "lima", "-f", privPath)

View on GitHub (pinned to dd909d0973)

Solutions

  1. Verify $LIMA_HOME (default ~/.lima) is writable by the current user; create it manually with mkdir -p ~/.lima/_config and chmod 700.
  2. If a regular file named _config exists, remove or rename it.
  3. Check the wrapped OS error in the message for the concrete cause (EACCES, ENOSPC, ENOTDIR).

Example fix

// before
export LIMA_HOME=/mnt/readonly/lima
limactl create
// after
export LIMA_HOME=$HOME/.lima
mkdir -p "$LIMA_HOME/_config" && chmod 700 "$LIMA_HOME/_config"
limactl create
Defensive patterns

Strategy: validation

Validate before calling

configDir := filepath.Join(limaHome, "_config")
if info, err := os.Stat(configDir); err == nil && !info.IsDir() {
    return fmt.Errorf("%s exists but is not a directory", configDir)
}
if err := os.MkdirAll(configDir, 0o700); err != nil { return err }

Type guard

func writableDir(p string) bool { return os.MkdirAll(p, 0o700) == nil }

Try / catch

keys, err := sshutil.DefaultPubKeys(ctx)
if err != nil {
    if strings.Contains(err.Error(), "could not create") {
        // check LIMA_HOME writability and report to user
    }
    return err
}

Prevention

When it happens

Trigger: Calling DefaultPubKeys (directly or via templateArgs) when _config does not exist yet and MkdirAll fails: read-only $LIMA_HOME, permission denied on the parent, disk full, or the path exists as a non-directory (a file named _config).

Common situations: LIMA_HOME pointing at a read-only or non-writable location; a stale file where the _config directory should be; running under a user without write access to the home dir; NFS/Windows mount quirks.

Related errors


AI-assisted analysis of lima-vm/lima@dd909d0973 (2026-09-01). Data as JSON: /api/errors/442e41f84e306316. Report an issue: GitHub.