pranshuparmar/witr · error
failed to convert plist: %w
Error message
failed to convert plist: %w
What it means
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.
Source
Thrown at internal/launchd/plist.go:156
if strings.HasPrefix(path, "~") {
path = filepath.Join(homeDir, path[1:])
}
plistPath := filepath.Join(path, label+".plist")
if _, err := os.Stat(plistPath); err == nil {
return plistPath
}
}
return ""
}
// ParsePlist reads and parses a launchd plist file
func ParsePlist(path string) (*LaunchdInfo, error) {
// Use plutil to convert to XML (handles binary plists)
out, err := exec.Command("plutil", "-convert", "xml1", "-o", "-", path).Output()
if err != nil {
return nil, fmt.Errorf("failed to convert plist: %w", err)
}
info := &LaunchdInfo{
PlistPath: path,
}
// Parse the XML plist
if err := parsePlistXML(out, info); err != nil {
return nil, err
}
return info, nil
}
// parsePlistXML parses XML plist data into LaunchdInfo
func parsePlistXML(data []byte, info *LaunchdInfo) error {
decoder := xml.NewDecoder(bytes.NewReader(data))
View on GitHub (pinned to dc4fa1da82)
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.
Example fix
// before
path := "/System/Library/LaunchDaemons/com.example.daemon.plist" // permission denied
info, err := launchd.ParsePlist(path)
// after
if _, err := os.Stat(path); os.IsNotExist(err) {
path = filepath.Join(os.Getenv("HOME"), "Library/LaunchAgents/com.example.daemon.plist")
}
info, err := launchd.ParsePlist(path) Defensive patterns
Strategy: validation
Validate before calling
func plistReadable(path string) error {
info, err := os.Stat(path)
if err != nil { return fmt.Errorf("plist missing: %w", err) }
if info.IsDir() { return fmt.Errorf("%s is a directory", path) }
f, err := os.Open(path)
if err != nil { return fmt.Errorf("plist unreadable: %w", err)
}
f.Close()
return nil
}
if err := plistReadable(path); err != nil { return err } Try / catch
info, err := launchd.ParsePlist(path)
if err != nil {
var ee *exec.ExitError
if errors.As(err, &ee) {
return fmt.Errorf("plutil rejected %s: %s", path, strings.TrimSpace(string(ee.Stderr)))
}
return err
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- launchctl blame failed: %w
- no service label found for pid %d
- process not managed by a named launchd service: %s
- listfds returned partial record
- invalid pid %d
AI-assisted analysis of pranshuparmar/witr@dc4fa1da82 (2026-09-01).
Data as JSON: /api/errors/6c781347100ad64b.
Report an issue: GitHub.