{"record":{"id":"6c781347100ad64b","repo":"pranshuparmar/witr","slug":"failed-to-convert-plist-w","errorCode":null,"errorMessage":"failed to convert plist: %w","messagePattern":"failed to convert plist: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/launchd/plist.go","lineNumber":156,"sourceCode":"\t\tif strings.HasPrefix(path, \"~\") {\n\t\t\tpath = filepath.Join(homeDir, path[1:])\n\t\t}\n\n\t\tplistPath := filepath.Join(path, label+\".plist\")\n\t\tif _, err := os.Stat(plistPath); err == nil {\n\t\t\treturn plistPath\n\t\t}\n\t}\n\n\treturn \"\"\n}\n\n// ParsePlist reads and parses a launchd plist file\nfunc ParsePlist(path string) (*LaunchdInfo, error) {\n\t// Use plutil to convert to XML (handles binary plists)\n\tout, err := exec.Command(\"plutil\", \"-convert\", \"xml1\", \"-o\", \"-\", path).Output()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to convert plist: %w\", err)\n\t}\n\n\tinfo := &LaunchdInfo{\n\t\tPlistPath: path,\n\t}\n\n\t// Parse the XML plist\n\tif err := parsePlistXML(out, info); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn info, nil\n}\n\n// parsePlistXML parses XML plist data into LaunchdInfo\nfunc parsePlistXML(data []byte, info *LaunchdInfo) error {\n\tdecoder := xml.NewDecoder(bytes.NewReader(data))\n","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/pranshuparmar/witr/blob/dc4fa1da82d3e266fcbd928641b4f30b3077c64f/internal/launchd/plist.go#L138-L174","documentation":"ParsePlist shells out to `plutil -convert xml1 -o -` to normalize (possibly binary) launchd plists into XML before parsing. Any failure of that external command — missing plutil, unreadable file, corrupt plist — is wrapped as 'failed to convert plist: %w'. The wrapped error contains plutil's own diagnostic.","triggerScenarios":"Calling GetLaunchdInfo/ParsePlist when plutil exits non-zero: plist path does not exist or is unreadable (permissions), the file is not a valid plist, or plutil is absent (non-macOS or stripped environment).","commonSituations":"Stale plist paths from LaunchAgents/LaunchDaemons referencing deleted files; inspecting a service label whose plist lives in a root-only directory; sandboxed/CI hosts without plutil; third-party plists with XML syntax errors.","solutions":["Verify the plist path exists and is readable (`ls -l <path>`); the wrapped error usually says 'no such file' vs 'permission denied'.","Re-run with sudo if the plist is in a protected directory (/System/Library/LaunchDaemons).","Validate the file manually with `plutil -lint <path>` to see the specific plist defect.","Confirm you're on macOS with plutil in PATH.","Locate the correct plist via `launchctl print <domain>/<label>` if the recorded path is stale."],"exampleFix":"// before\npath := \"/System/Library/LaunchDaemons/com.example.daemon.plist\" // permission denied\ninfo, err := launchd.ParsePlist(path)\n// after\nif _, err := os.Stat(path); os.IsNotExist(err) {\n    path = filepath.Join(os.Getenv(\"HOME\"), \"Library/LaunchAgents/com.example.daemon.plist\")\n}\ninfo, err := launchd.ParsePlist(path)","handlingStrategy":"validation","validationCode":"func plistReadable(path string) error {\n    info, err := os.Stat(path)\n    if err != nil { return fmt.Errorf(\"plist missing: %w\", err) }\n    if info.IsDir() { return fmt.Errorf(\"%s is a directory\", path) }\n    f, err := os.Open(path)\n    if err != nil { return fmt.Errorf(\"plist unreadable: %w\", err)\n    }\n    f.Close()\n    return nil\n}\nif err := plistReadable(path); err != nil { return err }","typeGuard":null,"tryCatchPattern":"info, err := launchd.ParsePlist(path)\nif err != nil {\n    var ee *exec.ExitError\n    if errors.As(err, &ee) {\n        return fmt.Errorf(\"plutil rejected %s: %s\", path, strings.TrimSpace(string(ee.Stderr)))\n    }\n    return err\n}","preventionTips":["Stat and open-test the plist path before parsing.","Validate plists with `plutil -lint` in tooling before relying on them.","Run with privileges matching the plist location (LaunchDaemons need root).","Resolve the current plist path via `launchctl print <domain>/<label>` instead of hard-coding."],"tags":["macos","launchd","plist","external-command"],"backgroundTag":"plist-parse-failed","analyzedSha":"dc4fa1da82d3e266fcbd928641b4f30b3077c64f","analyzedAt":"2026-09-01T12:17:08.767Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}