lima-vm/lima · error

failed to read boot scripts directory %#q

Error message

failed to read boot scripts directory %#q: %w

What it means

runBootScripts enumerates per-once and per-boot script directories (cloud-init style, e.g. /var/lib/cloud/scripts/per-boot) with os.ReadDir. If a directory cannot be read, provisioning fails with this wrapped error instead of continuing. This typically means the directory is missing or unreadable.

Solutions

  1. Create the missing directory in the guest (e.g. `sudo mkdir -p /var/lib/cloud/scripts/per-boot`).
  2. Make provisioning create the directories (os.MkdirAll) before runBootScripts reads them, or treat ENOENT as empty.
  3. Check the wrapped error: ENOENT means missing dir; EACCES means permission problem - fix ownership accordingly.
  4. Ensure no earlier user-data step removes or replaces /var/lib/cloud.

Example fix

// before
dirEntries, err := os.ReadDir(dir)
// after
if err := os.MkdirAll(dir, 0o755); err != nil { ... }
dirEntries, err := os.ReadDir(dir)
Defensive patterns

Strategy: fallback

Validate before calling

for _, dir := range []string{"/var/lib/cloud/scripts/per-once", "/var/lib/cloud/scripts/per-boot"} {
    if fi, err := os.Stat(dir); err != nil || !fi.IsDir() {
        os.MkdirAll(dir, 0o755)
    }
}

Try / catch

if err := runBootScripts(ctx); err != nil {
    var pe *fs.PathError
    if errors.As(err, &pe) && errors.Is(pe.Err, syscall.ENOENT) {
        // directory missing: create it or treat as no scripts
    }
}

Prevention

When it happens

Trigger: One of the boot-script directories (per-once or /var/lib/cloud/scripts/per-boot) does not exist on the macOS guest, is a file instead of a directory, or the process lacks read permission for it.

Common situations: Fresh macOS guest where the cloud-init directory layout was never created; a previous provisioning step deleted the directory; mounting a volume over /var/lib/cloud that hides the scripts dir.

Related errors


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

Appendix: source

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

	// FIXME: avoid hardcoding the primary network name
	const primaryNetwork = "Ethernet"
	cmd := exec.CommandContext(ctx, "networksetup", append([]string{"-setdnsservers", primaryNetwork}, resolvConf.Nameservers...)...)
	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 runBootScripts(ctx context.Context) error {
	dirs := []string{
		"/var/lib/cloud/scripts/per-boot",
	}
	var errs []error
	for _, dir := range dirs {
		dirEntries, err := os.ReadDir(dir)
		if err != nil {
			return fmt.Errorf("failed to read boot scripts directory %#q: %w", dir, err)
		}
		for _, entry := range dirEntries {
			if entry.IsDir() {
				continue
			}
			scriptPath := filepath.Join(dir, entry.Name())
			// entry.Type().Mode() does not seem to contain permission bits
			entryInfo, err := entry.Info()
			if err != nil {
				logrus.Warnf("Skipping boot script %#q due to stat error: %v", scriptPath, err)
				continue
			}
			if entryInfo.Mode().Perm()&0o111 == 0 {
				logrus.Warnf("Skipping non-executable boot script %#q (%v)", scriptPath, entryInfo.Mode().Perm())
				continue
			}
			cmd := exec.CommandContext(ctx, scriptPath)
			cmd.Stdout = os.Stdout

View on GitHub (pinned to dd909d0973)