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
- Verify ioreg output shape: `ioreg -rd1 -c IOPlatformExpertDevice` should show a top-level dict with IORegistryEntryChildren.
- Ensure the exact ioreg flags used by lima are unchanged; update lima if the macOS version changed output format.
- Check that nothing (aliases, wrappers, PATH hijacks) is substituting a different ioreg binary.
- 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
- Never replace or wrap the ioreg binary in PATH
- Test plist parsing with fixture plists matching real ioreg -rd1 output
- Pin known-good ioreg flags when mocking for tests
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
- invalid plist: IORegistryEntryChildren not found or empty
- invalid plist: IOPlatformUUID not found in any child of IORe
- failed to unmarshal xml: %w
- unexpected plist format: missing root dict
- failed to create temp file: %w
AI-assisted analysis of lima-vm/lima@dd909d0973 (2026-09-01).
Data as JSON: /api/errors/14485bbdef7883cf.
Report an issue: GitHub.