lima-vm/lima · error

invalid plist: top-level value is not a dict

Error message

invalid plist: top-level value is not a dict

What it means

On macOS, machineID() shells out to ioreg and parses the returned XML plist via parseIOPlatformUUIDFromIOPlatformExpertDevice. This error is returned when the plist decoded successfully but its top-level value is not a dictionary, meaning the input is not the expected ioreg plist structure.

Source

Thrown at pkg/osutil/machineid.go:68

		// We don't use "/sys/class/dmi/id/product_uuid"
	}
	for _, f := range candidates {
		b, err := os.ReadFile(f)
		if err == nil {
			return strings.TrimSpace(string(b)), nil
		}
	}
	return "", fmt.Errorf("no machine-id found, tried %v", candidates)
}

func parseIOPlatformUUIDFromIOPlatformExpertDevice(r io.Reader) (string, error) {
	var p plist.Plist
	dec := xml.NewDecoder(r)
	if err := dec.Decode(&p); err != nil {
		return "", err
	}
	if p.Value.Dict == nil {
		return "", errors.New("invalid plist: top-level value is not a dict")
	}
	ioRegistryEntryChildren, ok := p.Value.Dict["IORegistryEntryChildren"]
	if !ok || ioRegistryEntryChildren.Array == nil || len(ioRegistryEntryChildren.Array) == 0 {
		return "", errors.New("invalid plist: IORegistryEntryChildren not found or empty")
	}
	for _, child := range ioRegistryEntryChildren.Array {
		if child.Dict == nil {
			continue
		}
		ioPlatformUUID, ok := child.Dict["IOPlatformUUID"]
		if !ok || ioPlatformUUID.String == nil {
			continue
		}
		return *ioPlatformUUID.String, nil
	}

	return "", errors.New("invalid plist: IOPlatformUUID not found in any child of IORegistryEntryChildren")
}

View on GitHub (pinned to dd909d0973)

Solutions

  1. Verify ioreg output shape: `ioreg -rd1 -c IOPlatformExpertDevice` should show a top-level dict with IORegistryEntryChildren.
  2. Ensure the exact ioreg flags used by lima are unchanged; update lima if the macOS version changed output format.
  3. Check that nothing (aliases, wrappers, PATH hijacks) is substituting a different ioreg binary.
  4. Rely on the fallback: the error propagates up and machineID falls back to hostname; ensure hostname is unique.

Example fix

// before: feeding an array-root plist
<array></array>
// after: expected dict root
<?xml...><plist version="1.0"><dict>...</dict></plist>
Defensive patterns

Strategy: validation

Validate before calling

// before relying on machineID on macOS, sanity-check ioreg output
out, err := exec.Command("ioreg", "-rd1", "-c", "IOPlatformExpertDevice").Output()
if err != nil || !bytes.Contains(out, []byte("<dict>")) {
    // plist will not have a dict root; expect "invalid plist" errors
}

Type guard

func hasDictRoot(r io.Reader) bool {
    var p plist.Plist
    if err := xml.NewDecoder(r).Decode(&p); err != nil {
        return false
    }
    return p.Value.Dict != nil
}

Try / catch

id, err := osutil.MachineID(ctx)
if err != nil && strings.Contains(err.Error(), "invalid plist") {
    log.WithError(err).Warn("ioreg plist parse failed; falling back to hostname")
}

Prevention

When it happens

Trigger: machineID() is called on macOS and the plist bytes fed to parseIOPlatformUUIDFromIOPlatformExpertDevice decode to a non-dict top level (e.g. an array, string, or the plist library produced an unexpected root) — also exercised directly by TestParseIOPlatformUUIDFromIOPlatformExpertDevice.

Common situations: A non-standard ioreg output version or a mocked/piped plist that is a plain <array> or <string>; intercepting tools replacing ioreg with something producing a different plist shape; macOS version changes altering plist layout.

Related errors


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