lima-vm/lima · error

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

Error message

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

What it means

During migration of the legacy per-instance disk layout, the driver tries to create a symlink from the legacy diffdisk filename to the new disk filename. If os.Symlink fails (permissions, existing file, cross-device, no symlink support), the error is wrapped and returned, aborting migration and instance start.

Source

Thrown at pkg/driverutil/disk.go:37

	"github.com/lima-vm/lima/v2/pkg/iso9660util"
	"github.com/lima-vm/lima/v2/pkg/limatype/filenames"
	"github.com/lima-vm/lima/v2/pkg/osutil"
)

// MigrateDiskLayout creates symlinks from the current filenames (disk, iso) to
// the legacy filenames (diffdisk, basedisk) used by older Lima versions.
// The original files are left in place so older Lima versions can still use them.
func MigrateDiskLayout(instDir string) error {
	diskPath := filepath.Join(instDir, filenames.Disk)
	if osutil.FileExists(diskPath) {
		return nil // already migrated or new instance
	}

	diffDiskPath := filepath.Join(instDir, filenames.DiffDiskLegacy)
	if osutil.FileExists(diffDiskPath) {
		logrus.Infof("Creating symlink %#q -> %#q", filenames.Disk, filenames.DiffDiskLegacy)
		if err := os.Symlink(filenames.DiffDiskLegacy, diskPath); err != nil {
			return fmt.Errorf("failed to symlink %#q to %#q: %w", filenames.Disk, filenames.DiffDiskLegacy, err)
		}
	}

	baseDiskPath := filepath.Join(instDir, filenames.BaseDiskLegacy)
	isoPath := filepath.Join(instDir, filenames.ISO)
	if osutil.FileExists(baseDiskPath) && !osutil.FileExists(isoPath) {
		isISO, err := iso9660util.IsISO9660(baseDiskPath)
		if err != nil {
			return err
		}
		if isISO {
			logrus.Infof("Creating symlink %#q -> %#q", filenames.ISO, filenames.BaseDiskLegacy)
			if err := os.Symlink(filenames.BaseDiskLegacy, isoPath); err != nil {
				return fmt.Errorf("failed to symlink %#q to %#q: %w", filenames.ISO, filenames.BaseDiskLegacy, err)
			}
		}
		// Non-ISO basedisk is a legacy qcow2 backing file; leave it for QEMU to resolve.
	}

View on GitHub (pinned to dd909d0973)

Solutions

  1. Inspect the wrapped cause; if 'disk' already exists, remove the stale target and retry
  2. Fix directory permissions on the instance directory under LIMA_HOME
  3. Pre-migrate manually: delete stray 'disk' file, then `ln -s diffdisk disk` inside the instance dir
  4. If symlinks are unsupported on the volume, move LIMA_HOME to an NTFS/native-symlink-capable volume

Example fix

# before (in instance dir)
$ limactl start inst  # fails: failed to symlink "disk" to "diffdisk": file exists
# after
$ rm inst/disk && ln -s diffdisk inst/disk && limactl start inst
Defensive patterns

Strategy: try-catch

Validate before calling

func canMigrate(instDir string) error {
	if _, err := os.Lstat(filepath.Join(instDir, "disk")); err == nil {
		return fmt.Errorf("stale 'disk' file exists in %s", instDir)
	}
	return os.accessCheckWritable(instDir) // ensure writable, symlink-capable fs
}

Type guard

func symlinkSupported(dir string) bool {
	t := filepath.Join(dir, ".lima-link-test")
	if err := os.Symlink("target", t); err != nil { return false }
	_ = os.Remove(t)
	return true
}

Try / catch

if err := driverutil.MigrateDiskLayout(instDir); err != nil {
	var pe *fs.PathError
	if errors.As(err, &pe) && errors.Is(pe.Err, syscall.EEXIST) {
		os.Remove(filepath.Join(instDir, "disk"))
		return driverutil.MigrateDiskLayout(instDir)
	}
	return err
}

Prevention

When it happens

Trigger: MigrateDiskLayout (called from Prepare) finds <instDir>/diffdisk present and attempts os.Symlink("diffdisk", <instDir>/disk); the symlink call fails, e.g. because 'disk' already exists, the directory is read-only, or the filesystem does not allow symlinks (common on Windows without privileges).

Common situations: Starting an instance created by an older Lima version after a partially failed migration; running on Windows/FAT filesystems lacking symlink support; leftover files from a crashed run.

Related errors


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