pranshuparmar/witr · error
launchctl blame failed: %w
Error message
launchctl blame failed: %w
What it means
GetServiceLabel runs `launchctl blame <pid>` to discover which launchd service owns a process. Any non-zero exit or failure to capture output from that external command is wrapped in this error. Since launchctl blame only works on macOS and requires appropriate privileges, environment issues commonly surface here.
Source
Thrown at internal/launchd/plist.go:64
Array []string
Dict *plistDict
}
// plist search paths in order of precedence
var plistSearchPaths = []string{
"~/Library/LaunchAgents",
"/Library/LaunchAgents",
"/Library/LaunchDaemons",
"/System/Library/LaunchAgents",
"/System/Library/LaunchDaemons",
}
// GetServiceLabel uses launchctl blame to get the service label for a PID
func GetServiceLabel(pid int) (string, string, error) {
// launchctl blame <pid> returns the service that started the process
out, err := exec.Command("launchctl", "blame", strconv.Itoa(pid)).Output()
if err != nil {
return "", "", fmt.Errorf("launchctl blame failed: %w", err)
}
// Output format varies:
// - "system/com.apple.example" or "gui/501/com.example.app" (real service)
// - "speculative", "non-ipc demand", "launch job demand", "ipc (mach)" (blame reasons)
line := strings.TrimSpace(string(out))
if line == "" {
return "", "", fmt.Errorf("no service label found for pid %d", pid)
}
// Check if this is a real service path (contains "/" and starts with domain)
if !strings.Contains(line, "/") {
// This is a blame reason, not a service label
// Try to find the service by querying launchctl list
label, domain := findServiceByPID(pid)
if label != "" {
return label, domain, nil
}View on GitHub (pinned to dc4fa1da82)
Solutions
- Run on macOS and ensure launchctl exists (`which launchctl`).
- Retry with elevated privileges (sudo) if the target process belongs to another user.
- Confirm the pid is still alive before calling (signal 0 check).
- If blame is unsupported for that process, fall back to parsing the process environment or skip launchd enrichment.
- Inspect the wrapped %w error to distinguish exec-not-found from command failure.
Example fix
// before
label, domain, err := launchd.GetServiceLabel(pid) // fails on Linux
// after
if runtime.GOOS != "darwin" {
return nil, fmt.Errorf("launchd info only available on macOS")
}
label, domain, err := launchd.GetServiceLabel(pid) Defensive patterns
Strategy: fallback
Validate before calling
if runtime.GOOS != "darwin" {
return errors.New("launchd enrichment requires macOS")
}
if _, err := exec.LookPath("launchctl"); err != nil {
return errors.New("launchctl not available in PATH")
} Try / catch
label, domain, err := launchd.GetServiceLabel(pid)
if err != nil {
var ee *exec.ExitError
if errors.As(err, &ee) && strings.Contains(err.Error(), "launchctl blame failed") {
log.Printf("blame unavailable for pid %d (privileges or unsupported): %v", pid, err)
return fallbackEnrichment(pid) // e.g. ps/env-based info
}
return err
} Prevention
- Gate launchd calls behind runtime.GOOS == "darwin" checks.
- Run with sudo when inspecting other users' processes.
- Check the pid is alive immediately before calling.
- Inspect the wrapped %w error to distinguish missing launchctl from command failure.
When it happens
Trigger: Calling GetLaunchdInfo/GetServiceLabel on macOS when launchctl blame exits non-zero: pid does not exist, insufficient privileges, launchctl not present, or non-macOS platform.
Common situations: Running on Linux/CI (no launchctl in PATH); inspecting a process owned by another user without sudo; the target pid exited between discovery and the blame call; sandboxed environments that block exec of launchctl.
Related errors
- no service label found for pid %d
- process not managed by a named launchd service: %s
- failed to convert plist: %w
- listfds returned partial record
- no process ancestry found
AI-assisted analysis of pranshuparmar/witr@dc4fa1da82 (2026-09-01).
Data as JSON: /api/errors/7c025adf6522b815.
Report an issue: GitHub.