-
Notifications
You must be signed in to change notification settings - Fork 26
Description
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_loadermatches (contains "loa" in "loader")gui/501/com.apple.mDNSResponderHelper.reloadedmatches (contains "loa" in "reloaded")gui/501/loamatches (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:
- Use
launchctl list <label>to check if the service is installed - Construct the domain target directly (
gui/<uid>/<label>for user,system/<label>for system) instead of parsing error output - Use
launchctl print <target>to checkstate = 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)