Skip to content

πŸ› LaunchdServiceManager::status returns Stopped for running services on macOSΒ #41

@anthonyra

Description

@anthonyra

The status() method on LaunchdServiceManager returns ServiceStatus::Stopped(None) for services that are actively running with a PID.

Root Cause

The current implementation uses launchctl print <label> which requires a full domain-qualified path (e.g., gui/501/my-service). When called with just the label, it fails with exit code 64 and suggests possible matches.

The code then parses the error output to find the full path using substring matching:

let label = out.lines().find(|line| line.contains(&service_name));

This is buggy because other services may contain the label as a substring. For example, with label loa:

  • system/com.apple.iomfb_fdr_loader matches (contains "loa" in "loader")
  • gui/501/com.apple.mDNSResponderHelper.reloaded matches (contains "loa" in "reloaded")
  • gui/501/loa matches (the actual service)

Since find() returns the first match, it picks the wrong service.

Suggested Fix

Use a combination of launchctl list and launchctl print with a constructed domain target:

  1. Use launchctl list <label> to check if the service is installed
  2. Construct the domain target directly (gui/<uid>/<label> for user, system/<label> for system) instead of parsing error output
  3. Use launchctl print <target> to check state = running

This avoids the substring matching issue entirely by constructing the full path based on the service level.

Environment

  • macOS (tested on Darwin 24.6.0)
  • service-manager 0.10.0
  • User-level launchd service

Reproduction

use service_manager::{
    LaunchdServiceManager, ServiceManager, ServiceStatusCtx, ServiceLabel,
};

fn main() {
    let manager = LaunchdServiceManager::user();
    let label: ServiceLabel = "loa".parse().unwrap();

    let status = manager.status(ServiceStatusCtx { label }).unwrap();
    println!("status: {:?}", status);  // Prints: Stopped(None) even when running
}

Verification

# Service IS running:
$ launchctl list loa
{
    "Label" = "loa";
    "PID" = 19897;
    ...
};

# But status() returns Stopped(None)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions