{"record":{"id":"14485bbdef7883cf","repo":"lima-vm/lima","slug":"invalid-plist-top-level-value-is-not-a-dict","errorCode":null,"errorMessage":"invalid plist: top-level value is not a dict","messagePattern":"invalid plist: top-level value is not a dict","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/osutil/machineid.go","lineNumber":68,"sourceCode":"\t\t// We don't use \"/sys/class/dmi/id/product_uuid\"\n\t}\n\tfor _, f := range candidates {\n\t\tb, err := os.ReadFile(f)\n\t\tif err == nil {\n\t\t\treturn strings.TrimSpace(string(b)), nil\n\t\t}\n\t}\n\treturn \"\", fmt.Errorf(\"no machine-id found, tried %v\", candidates)\n}\n\nfunc parseIOPlatformUUIDFromIOPlatformExpertDevice(r io.Reader) (string, error) {\n\tvar p plist.Plist\n\tdec := xml.NewDecoder(r)\n\tif err := dec.Decode(&p); err != nil {\n\t\treturn \"\", err\n\t}\n\tif p.Value.Dict == nil {\n\t\treturn \"\", errors.New(\"invalid plist: top-level value is not a dict\")\n\t}\n\tioRegistryEntryChildren, ok := p.Value.Dict[\"IORegistryEntryChildren\"]\n\tif !ok || ioRegistryEntryChildren.Array == nil || len(ioRegistryEntryChildren.Array) == 0 {\n\t\treturn \"\", errors.New(\"invalid plist: IORegistryEntryChildren not found or empty\")\n\t}\n\tfor _, child := range ioRegistryEntryChildren.Array {\n\t\tif child.Dict == nil {\n\t\t\tcontinue\n\t\t}\n\t\tioPlatformUUID, ok := child.Dict[\"IOPlatformUUID\"]\n\t\tif !ok || ioPlatformUUID.String == nil {\n\t\t\tcontinue\n\t\t}\n\t\treturn *ioPlatformUUID.String, nil\n\t}\n\n\treturn \"\", errors.New(\"invalid plist: IOPlatformUUID not found in any child of IORegistryEntryChildren\")\n}","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/lima-vm/lima/blob/dd909d0973cd84fa35f9e1693181b4585ea616c1/pkg/osutil/machineid.go#L50-L86","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: feeding an array-root plist\n<array></array>\n// after: expected dict root\n<?xml...><plist version=\"1.0\"><dict>...</dict></plist>","handlingStrategy":"validation","validationCode":"// before relying on machineID on macOS, sanity-check ioreg output\nout, err := exec.Command(\"ioreg\", \"-rd1\", \"-c\", \"IOPlatformExpertDevice\").Output()\nif err != nil || !bytes.Contains(out, []byte(\"<dict>\")) {\n    // plist will not have a dict root; expect \"invalid plist\" errors\n}","typeGuard":"func hasDictRoot(r io.Reader) bool {\n    var p plist.Plist\n    if err := xml.NewDecoder(r).Decode(&p); err != nil {\n        return false\n    }\n    return p.Value.Dict != nil\n}","tryCatchPattern":"id, err := osutil.MachineID(ctx)\nif err != nil && strings.Contains(err.Error(), \"invalid plist\") {\n    log.WithError(err).Warn(\"ioreg plist parse failed; falling back to hostname\")\n}","preventionTips":["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"],"tags":["macos","plist","machine-id"],"backgroundTag":"invalid-plist-structure","analyzedSha":"dd909d0973cd84fa35f9e1693181b4585ea616c1","analyzedAt":"2026-09-01T14:24:59.842Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}