lima-vm/lima · error

daemon for %#q network failed to start after attempts

Error message

%s daemon for %#q network failed to start after %d attempts: %w%s

What it means

The network daemon exited before writing its PID file on every one of the 5 retry attempts (with 1s/2s/4s/8s backoff). This retry loop exists specifically for the transient macOS VMNET_FAILURE XPC race with com.apple.NetworkSharing at boot; exhausting all attempts means the daemon is deterministically failing. The daemon's stderr (or log path) is appended to explain why it exits.

Solutions

  1. Inspect the daemon stderr in the message (or the log file under ~/.lima/networks) for the concrete exit reason
  2. Verify socket_vmnet is installed and current: brew install/reinstall socket_vmnet
  3. Check networks.yaml: socketVMNet path and the network switch device must exist on this host
  4. Reboot the Mac so com.apple.NetworkSharing is fully initialized, then retry
  5. Regenerate the sudoers file if socket_vmnet was upgraded: limactl sudoers | sudo tee /etc/sudoers.d/lima

Example fix

// before
limactl start  # fails after 5 attempts: socket_vmnet VMNET_FAILURE
// after
brew reinstall socket_vmnet
limactl sudoers | sudo tee /etc/sudoers.d/lima
limactl start
Defensive patterns

Strategy: retry

Validate before calling

if _, err := os.Stat(socketVMNetPath); err != nil {
    return fmt.Errorf("socket_vmnet not installed at %s: %w", socketVMNetPath, err)
}
if err := exec.Command(socketVMNetPath, "--version").Run(); err != nil {
    return fmt.Errorf("socket_vmnet binary broken: %w", err)
}

Type guard

func isDaemonExitedError(err error) bool {
    var e *daemonExitedError
    return errors.As(err, &e)
}

Try / catch

if err := startNetwork(); err != nil {
    if strings.Contains(err.Error(), "failed to start after") {
        // deterministic daemon failure: check stderr hint, reinstall socket_vmnet
    }
    return err
}

Prevention

When it happens

Trigger: startDaemonWithRetry observes *daemonExitedError five consecutive times — the daemon binary starts and exits each time before creating its PID file, e.g. socket_vmnet crashing on VMNET_FAILURE, bad arguments, missing network device, or a genuinely broken install.

Common situations: macOS right after boot when the race is not transient (broken socket_vmnet), socket_vmnet version mismatch with the generated sudoers config, the configured switch device (e.g. bridge100) not existing, or the daemon lacking permission to open the VMNET interface.

Related errors


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

Appendix: source

Thrown at pkg/networks/reconcile/reconcile.go:318

			return err // infrastructure failure (sudo/exec) — not transient
		}
		err = waitForDaemon(ctx, cmd, pidFile, startTimeout)
		if err == nil {
			return nil
		}
		// Only an early exit (the transient XPC race) is retried; everything else
		// is surfaced immediately.
		if _, ok := errors.AsType[*daemonExitedError](err); !ok {
			// A stuck (still-running) daemon gets its stderr appended; PID-read and
			// context errors propagate as-is.
			if _, ok := errors.AsType[*daemonStuckError](err); ok {
				return fmt.Errorf("%s daemon for %#q network %w%s", daemon, name, err, stderrHint(stderrLog))
			}
			return err
		}
		lastErr = err // process exited early — retry
	}
	return fmt.Errorf("%s daemon for %#q network failed to start after %d attempts: %w%s",
		daemon, name, maxAttempts, lastErr, stderrHint(stderrLog))
}

// waitForDaemon waits until the daemon writes its PID file (success), exits without
// one (returns *daemonExitedError so the caller can retry), or stays alive past
// timeout without a PID file (returns *daemonStuckError). A stuck or cancelled
// process is killed and reaped.
func waitForDaemon(ctx context.Context, cmd *exec.Cmd, pidFile string, timeout time.Duration) error {
	waitCh := make(chan error, 1)
	go func() { waitCh <- cmd.Wait() }()

	timer := time.NewTimer(timeout)
	defer timer.Stop()
	ticker := time.NewTicker(500 * time.Millisecond)
	defer ticker.Stop()

	for {
		ready, err := pidFileWritten(pidFile)

View on GitHub (pinned to dd909d0973)