lima-vm/lima · error

failed to create symlink from %#q to %#q: %w

Error message

failed to create symlink from %#q to %#q: %w

What it means

Raised by mountVirtiofs when os.Symlink fails while creating the destination symlink pointing to /Volumes/My Shared Files/<pseudoTag>. The OS-level reason (permission denied, path exists as a real file/directory, etc.) is wrapped in the message together with both link endpoints.

Source

Thrown at pkg/guestagent/fakecloudinit/fakecloudinit_darwin.go:182

	}
	lnSrc := filepath.Join("/Volumes/My Shared Files", pseudoTag)

	// FIXME: verify that the filesystem of lnSrc is indeed read-only when user-data contains the "ro" option.
	// unix.Statfs() could be used, but unix.Statfs_t.Flags & unix.MNT_RDONLY seems always 0 for virtiofs.
	// `mount -v` does not show "ro" flag either.

	lnExisting, err := os.Readlink(dir)
	if err == nil {
		if lnExisting == lnSrc {
			return nil // already symlinked
		}
		return fmt.Errorf("unexpected symlink target for virtiofs mount source %#q: expected %#q, got %#q", lnSrc, lnSrc, lnExisting)
	}
	if !errors.Is(err, os.ErrNotExist) {
		logrus.WithError(err).Warnf("Failed to read existing symlink for virtiofs mount source %#q", lnSrc)
	}
	if err := os.Symlink(lnSrc, dir); err != nil {
		return fmt.Errorf("failed to create symlink from %#q to %#q: %w", lnSrc, dir, err)
	}
	return nil
}

func setTimezone(ctx context.Context, timezone string) error {
	cmd := exec.CommandContext(ctx, "systemsetup", "-settimezone", timezone)
	logrus.Infof("Executing command: %v", cmd.Args)
	if output, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("failed to execute command %v: %w (output=%#q)", cmd.Args, err, output)
	}
	return nil
}

func generatePassword() (string, error) {
	const pwLen = 16
	// Avoid special characters to minimize potential keyboard layout issue in GUI
	pw, err := password.Generate(pwLen, pwLen/4, 0, false, false)
	if err != nil {

View on GitHub (pinned to dd909d0973)

Solutions

  1. Check the wrapped OS error in the message (EEXIST vs ENOENT vs EACCES).
  2. If dir exists as a real file/directory, remove or rename it in the guest so the symlink can be created.
  3. Create the parent directory of dir before the mounts step (e.g. via write_files or boot scripts).
  4. Verify /Volumes/My Shared Files/<pseudoTag> exists; if not, the share is not configured in the Lima YAML.
  5. Ensure the guestagent runs with sufficient privileges to write at the destination path.

Example fix

// inside guest VM
// before
$ ls -ld /mnt/share  # real directory
$ sudo rmdir /mnt/share
// after (next boot creates the symlink)
$ ls -l /mnt/share  # -> /Volumes/My Shared Files/share
Defensive patterns

Strategy: try-catch

Validate before calling

func ensureSymlinkable(dir string) error {
  if fi, err := os.Lstat(dir); err == nil {
    if fi.Mode()&os.ModeSymlink == 0 { return fmt.Errorf("%s exists and is not a symlink", dir) }
    return nil // symlink: readlink check happens separately
  } else if !errors.Is(err, os.ErrNotExist) {
    return err
  }
  return os.MkdirAll(filepath.Dir(dir), 0o755) // parent must exist
}

Type guard

func canCreateSymlink(dir string) bool {
  fi, err := os.Lstat(dir)
  return os.IsNotExist(err) || (err == nil && fi.Mode()&os.ModeSymlink != 0)
}

Try / catch

if err := os.Symlink(lnSrc, dir); err != nil {
  if errors.Is(err, os.ErrExist) {
    // destination occupied by a real file/dir: log and skip or clean up first
  }
  return fmt.Errorf("failed to create symlink from %#q to %#q: %w", lnSrc, dir, err)
}

Prevention

When it happens

Trigger: os.Symlink(lnSrc, dir) returns an error: `dir` already exists as a regular file or directory (EEXIST - note Readlink only detected ENOENT before), the parent of dir does not exist, or the agent lacks permission to write in the destination directory.

Common situations: A previous provisioning run left a real directory at the mount destination, the parent mount point was never created, or a read-only destination filesystem.

Related errors


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