Service Management framework supports installing and uninstalling services, including Service Management login items, launchd agents, and launchd daemons.
General:
Forums subtopic: App & System Services > Processes & Concurrency
Forums tag: Service Management
Service Management framework documentation
Daemons and Services Programming Guide archived documentation
Technote 2083 Daemons and Agents — It hasn’t been updated in… well… decades, but it’s still remarkably relevant.
EvenBetterAuthorizationSample sample code — This has been obviated by SMAppService.
SMJobBless sample code — This has been obviated by SMAppService.
Sandboxing with NSXPCConnection sample code
WWDC 2022 Session 10096 What’s new in privacy introduces the new SMAppService facility, starting at 07˸07
BSD Privilege Escalation on macOS forums post
Getting Started with SMAppService forums post
Background items showing up with the wrong name forums post
Related forums tags include:
XPC, Apple’s preferred inter-process communication (IPC) mechanism
Inter-process communication, for other IPC mechanisms
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Service Management
RSS for tagThe Service Management framework provides facilities to load and unload launched services and read and modify launched dictionaries from within an application.
Posts under Service Management tag
58 Posts
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
To establish a privileged helper daemon from a command line app to handle actions requiring root privileges I still use the old way of SMJobBless. But this is deprecated since OSX 10.13 and I want to finally update it to the new way using SMAppService.
As I'm concerned with securing it against malicious exploits, do you have a recommended up-to-date implementation in Objective-C establishing a privileged helper and verifying it is only used by my signed app?
I've seen the suggestion in the documentation to use SMAppService, but couldn't find a good implementation covering security aspects. My old implementation in brief is as follows:
bool runJobBless () {
// check if already installed
NSFileManager* filemgr = [NSFileManager defaultManager];
if ([filemgr fileExistsAtPath:@"/Library/PrivilegedHelperTools/com.company.Helper"] &&
[filemgr fileExistsAtPath:@"/Library/LaunchDaemons/com.company.Helper.plist"])
{
// check helper version to match the client
// ...
return true;
}
// create authorization reference
AuthorizationRef authRef;
OSStatus status = AuthorizationCreate (NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authRef);
if (status != errAuthorizationSuccess) return false;
// obtain rights to install privileged helper
AuthorizationItem authItem = { kSMRightBlessPrivilegedHelper, 0, NULL, 0 };
AuthorizationRights authRights = { 1, &authItem };
AuthorizationFlags flags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights;
status = AuthorizationCopyRights (authRef, &authRights, kAuthorizationEmptyEnvironment, flags, NULL);
if (status != errAuthorizationSuccess) return false;
// SMJobBless does it all: verify helper against app and vice-versa, place and load embedded launchd.plist in /Library/LaunchDaemons, place executable in /Library/PrivilegedHelperTools
CFErrorRef cfError;
if (!SMJobBless (kSMDomainSystemLaunchd, (CFStringRef)@"com.company.Helper", authRef, &cfError)) {
// check helper version to match the client
// ...
return true;
} else {
CFBridgingRelease (cfError);
return false;
}
}
void connectToHelper () {
// connect to helper via XPC
NSXPCConnection* c = [[NSXPCConnection alloc] initWithMachServiceName:@"com.company.Helper.mach" options:NSXPCConnectionPrivileged];
c.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol (SilentInstallHelperProtocol)];
[c resume];
// call function on helper and wait for completion
dispatch_semaphore_t semaphore = dispatch_semaphore_create (0);
[[c remoteObjectProxy] callFunction:^() {
dispatch_semaphore_signal (semaphore);
}];
dispatch_semaphore_wait (semaphore, dispatch_time (DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC));
dispatch_release (semaphore);
[c invalidate];
[c release];
}
I've recently upgraded to the RC candidates of macOS 26 and Xcode 26. The app I'm building has a helper tool using SMAppService. When I run the app and helper tool in macOS 15 or macOS 26, all works as expected. When it runs on macOS 13 or 14, which previously worked. The helper now crashes on launch with the following reason:
Termination Reason: CODESIGNING 4 Launch Constraint Violation
I found this developer session which seems to address this, but the plist I've added doesn't seem to satisfy the constraint.
https://developer.apple.com/videos/play/wwdc2023/10266/
Here are the contents of my new plist:
Are there any gotchas here that I might be missing?
Thanks!
I abandoned Mac development back around 10.4 when I departed Apple and am playing catch-up, trying to figure out how to register a privileged helper tool that can execute commands as root in the new world order. I am developing on 13.1 and since some of these APIs debuted in 13, I'm wondering if that's ultimately the root of my problem.
Starting off with the example code provided here:
https://developer.apple.com/documentation/servicemanagement/updating-your-app-package-installer-to-use-the-new-service-management-api
Following all build/run instructions in the README to the letter, I've not been successful in getting any part of it to work as documented. When I invoke the register command the test app briefly appears in System Settings for me to enable, but once I slide the switch over, it disappears. Subsequent attempts to invoke the register command are met only with the error message:
`Unable to register Error Domain=SMAppServiceErrorDomain Code=1 "Operation not permitted" UserInfo={NSLocalizedFailureReason=Operation not permitted}
The app does not re-appear in System Settings on these subsequent invocations. When I invoke the status command the result mysteriously equates to SMAppService.Status.notFound.
The plist is in the right place with the right name and it is using the BundleProgram key exactly as supplied in the sample code project. The executable is also in the right place at Contents/Resources/SampleLaunchAgent relative to the app root.
The error messaging here is extremely disappointing and I'm not seeing any way for me to dig any further without access to the underlying Objective-C (which the Swift header docs reference almost exclusively, making it fairly clear that this was a... Swift... Port... [Pun intended]).
I was stuck on a long train journey this weekend, so I thought I’d use that time to write up the process for installing a launchd daemon using SMAppService. This involves a number of deliberate steps and, while the overall process isn’t too hard — it’s certainly a lot better than with the older SMJobBless — it’s easy to accidentally stray from the path and get very confused.
If you have questions or comments, start a new thread in the App & System Services > Processes & Concurrency subtopic and tag it with Service Management.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Getting Started with SMAppService
This post explains how to use SMAppService to install a launchd daemon. I tested these instructions using Xcode 26.0 on macOS 15.6.1. Things are likely to be slightly different with different Xcode and macOS versions.
Create the container app target
To start, I created a new project:
I choose File > New > Project.
In the template picker, I chose macOS > App.
In options page, I set the Product Name field to SMAppServiceTest [1].
And I selected my team in the Team popup.
And I verified that the Organization Identifier was set to com.example.apple-samplecode, the standard for Apple sample code [1].
I selected SwiftUI in the Interface popup. There’s no requirement to use SwiftUI here; I chose it because that’s what I generally use these days.
And None in the Testing System popup.
And None in the Storage popup.
I then completed the new project workflow.
I configured basic settings on the project:
In the Project navigator, I selected the SMAppServiceTest project.
In the Project editor, I selected the SMAppServiceTest target.
At the top I selected Signing & Capabilities.
In the Signing section, I made sure that “Automatically manage signing” was checked.
And that my team was selected in the Team popup.
And that the bundle ID of the app ended up as com.example.apple-samplecode.SMAppServiceTest.
Still in the Signing & Capabilities tab, I removed the App Sandbox section.
Note It’s possible to use SMAppService to install a daemon from a sandboxed app, but in that case the daemon also has to be sandboxed. That complicates things, so I’m disabling the sandbox for the moment. See Enable App Sandbox, below, for more on this.
Next I tweaked some settings to make it easier to keep track of which target is which:
At the top, I selected the Build Settings tab.
I changed the Product Name build setting from $(TARGET_NAME) to SMAppServiceTest.
On the left, I renamed the target to App.
I chose Product > Scheme > Manage Schemes.
In the resulting sheet, I renamed the scheme from SMAppServiceTest to App, just to keep things in sync.
[1] You are free to choose your own value, of course. However, those values affect other values later in the process, so I’m giving the specific values I used so that you can see how everything lines up.
Create the daemon target
I then created a daemon target:
I chose File > New > Target.
In the template picker, I chose macOS > Command Line Tool.
In the options page, I set the Product Name field to Daemon.
And I selected my team in the Team popup.
And I verified that the Organization Identifier was set to com.example.apple-samplecode, the standard for Apple sample code.
I selected Swift in the Language popup.
And verified that SMAppServiceTest was set in the Project popup.
I clicked Finish.
I configured basic settings on the target:
In the Project navigator, I selected the SMAppServiceTest project.
In the Project editor, I selected the Daemon target.
At the top I selected Signing & Capabilities.
In the Signing section, I made sure that “Automatically manage signing” was checked.
And that my team was selected in the Team popup.
Note The Bundle Identifier field is blank, and that’s fine. There are cases where you want to give a daemon a bundle identifier, but it’s not necessary in this case.
Next I tweaked some settings to make it easier to keep track of which target is which:
At the top, I selected the Build Settings tab.
I changed the Product Name build setting from $(TARGET_NAME) to SMAppServiceTest-Daemon.
I forced the Enable Debug Dylib Support to No.
IMPORTANT To set it to No, you first have to set it to Yes and then set it back to No.
I edited Daemon/swift.swift to look like this:
import Foundation
import os.log
let log = Logger(subsystem: "com.example.apple-samplecode.SMAppServiceTest", category: "daemon")
func main() {
log.log("Hello Cruel World!")
dispatchMain()
}
main()
This just logs a ‘first light’ log message and parks [1] the main thread in dispatchMain().
Note For more about first light log points, see Debugging a Network Extension Provider.
[1] Technically the main thread terminates in this case, but I say “parks” because that’s easier to understand (-:
Test the daemon executable
I selected the Daemon scheme and chose Product > Run. The program ran, logging its first light log entry, and then started waiting indefinitely.
Note Weirdly, in some cases the first time I ran the program I couldn’t see its log output. I had to stop and re-run it. I’m not sure what that’s about.
I chose Product > Stop to stop it. I then switched back the App scheme.
Embed the daemon in the app
I added a build phase to embed the daemon executable into app:
In the Project navigator, I selected the SMAppServiceTest project.
In the Project editor, I selected the App target.
At the top I selected Build Phases.
I added a new copy files build phase.
I renamed it to Embed Helper Tools.
I set its Destination popup to Executables.
I clicked the add (+) button under the list and selected SMAppServiceTest-Daemon.
I made sure that Code Sign on Copy was checked for that.
I then created a launchd property list file for the daemon:
In the Project navigator, I selected SMAppServiceTestApp.swift.
I chose Product > New > File from Template.
I selected the Property List template.
In the save sheet, I named the file com.example.apple-samplecode.SMAppServiceTest-Daemon.plist.
And made sure that the Group popup was set to SMAppServiceTest.
And that only the App target was checked in the Targets list.
I clicked Create to create the file.
In the property list editor, I added two properties:
Label, with a string value of com.example.apple-samplecode.SMAppServiceTest-Daemon
BundleProgram, with a string value of Contents/MacOS/SMAppServiceTest-Daemon
I added a build phase to copy that property list into app:
In the Project navigator, I selected the SMAppServiceTest project.
In the Project editor, I selected the App target.
At the top I selected Build Phases.
I added a new copy files build phase.
I renamed it to Copy LaunchDaemons Property Lists.
I set its Destination popup to Wrapper.
And set the Subpath field to Contents/Library/LaunchDaemons.
I disclosed the contents of the Copy Bundle Resources build phase.
I dragged com.example.apple-samplecode.SMAppServiceTest-Daemon.plist from the Copy Bundle Resources build phase to the new Copy LaunchDaemons Property Lists build phase.
I made sure that Code Sign on Copy was unchecked.
Register and unregister the daemon
In the Project navigator, I selected ContentView.swift and added the following to the imports section:
import os.log
import ServiceManagement
I then added this global variable:
let log = Logger(subsystem: "com.example.apple-samplecode.SMAppServiceTest", category: "app")
Finally, I added this code to the VStack:
Button("Register") {
do {
log.log("will register")
let service = SMAppService.daemon(plistName: "com.example.apple-samplecode.SMAppServiceTest-Daemon.plist")
try service.register()
log.log("did register")
} catch let error as NSError {
log.log("did not register, \(error.domain, privacy: .public) / \(error.code)")
}
}
Button("Unregister") {
do {
log.log("will unregister")
let service = SMAppService.daemon(plistName: "com.example.apple-samplecode.SMAppServiceTest-Daemon.plist")
try service.unregister()
log.log("did unregister")
} catch let error as NSError {
log.log("did not unregister, \(error.domain, privacy: .public) / \(error.code)")
}
}
IMPORTANT None of this is code is structured as I would structure a real app. Rather, this is the absolutely minimal code needed to demonstrate this API.
Check the app structure
I chose Product > Build and verified that everything built OK. I then verified that the app’s was structured correctly:
I then choose Product > Show Build Folder in Finder.
I opened a Terminal window for that folder.
In Terminal, I changed into the Products/Debug directory and dumped the structure of the app:
% cd "Products/Debug"
% find "SMAppServiceTest.app"
SMAppServiceTest.app
SMAppServiceTest.app/Contents
SMAppServiceTest.app/Contents/_CodeSignature
SMAppServiceTest.app/Contents/_CodeSignature/CodeResources
SMAppServiceTest.app/Contents/MacOS
SMAppServiceTest.app/Contents/MacOS/SMAppServiceTest.debug.dylib
SMAppServiceTest.app/Contents/MacOS/SMAppServiceTest
SMAppServiceTest.app/Contents/MacOS/__preview.dylib
SMAppServiceTest.app/Contents/MacOS/SMAppServiceTest-Daemon
SMAppServiceTest.app/Contents/Resources
SMAppServiceTest.app/Contents/Library
SMAppServiceTest.app/Contents/Library/LaunchDaemons
SMAppServiceTest.app/Contents/Library/LaunchDaemons/com.example.apple-samplecode.SMAppServiceTest-Daemon.plist
SMAppServiceTest.app/Contents/Info.plist
SMAppServiceTest.app/Contents/PkgInfo
There are a few things to note here:
The com.example.apple-samplecode.SMAppServiceTest-Daemon.plist property list is in Contents/Library/LaunchDaemons.
The daemon executable is at Contents/MacOS/SMAppServiceTest-Daemon.
The app is still built as debug dynamic library (SMAppServiceTest.debug.dylib) but the daemon is not.
Test registration
I chose Product > Run. In the app I clicked the Register button. The program logged:
will register
did not register, SMAppServiceErrorDomain / 1
Error 1 indicates that installing a daemon hasn’t been approved by the user. The system also presented a notification:
Background Items Added
“SMAppServiceTest” added items that can
run in the background for all users. Do you
want to allow this?
Options > Allow
> Don’t Allow
I chose Allow and authenticated the configuration change.
In Terminal, I verified that the launchd daemon was loaded:
% sudo launchctl list com.example.apple-samplecode.SMAppServiceTest-Daemon
{
"LimitLoadToSessionType" = "System";
"Label" = "com.example.apple-samplecode.SMAppServiceTest-Daemon";
"OnDemand" = true;
"LastExitStatus" = 0;
"Program" = "Contents/MacOS/SMAppServiceTest-Daemon";
};
IMPORTANT Use sudo to target the global launchd context. If you omit this you end up targeting the launchd context in which Terminal is running, a GUI login context, and you won't find any launchd daemons there.
I started monitoring the system log:
I launched the Console app.
I pasted subsystem:com.example.apple-samplecode.SMAppServiceTest into the search box.
I clicked “Start streaming”.
Back in Terminal, I started the daemon:
% sudo launchctl start com.example.apple-samplecode.SMAppServiceTest-Daemon
In Console, I saw it log its first light log point:
type: default
time: 17:42:20.626447+0100
process: SMAppServiceTest-Daemon
subsystem: com.example.apple-samplecode.SMAppServiceTest
category: daemon
message: Hello Cruel World!
Note I’m starting the daemon manually because my goal here is to show how to use SMAppService, not how to use XPC to talk to a daemon. For general advice about XPC, see XPC Resources.
Clean up
Back in the app, I clicked Unregister. The program logged:
will unregister
did unregister
In Terminal, I confirmed that the launchd daemon was unloaded:
% sudo launchctl list com.example.apple-samplecode.SMAppServiceTest-Daemon
Could not find service "com.example.apple-samplecode.SMAppServiceTest-Daemon" in domain for system
Note This doesn’t clean up completely. The system remembers your response to the Background Items Added notification, so the next time you run the app and register your daemon it will be immediately available. To reset that state, run the sfltool with the resetbtm subcommand.
Install an Agent Rather Than a Daemon
The above process shows how to install a launchd daemon. Tweaking this to install a launchd agent is easy. There are only two required changes:
In the Copy Launch Daemon Plists copy files build phase, set the Subpath field to Contents/Library/LaunchAgents.
In ContentView.swift, change the two SMAppService.daemon(plistName:) calls to SMAppService.agent(plistName:).
There are a bunch of other changes you should make, like renaming everything from daemon to agent, but those aren’t required to get your agent working.
Enable App Sandbox
In some cases you might want to sandbox the launchd job (the term job to refer to either a daemon or an agent.) This most commonly crops up with App Store apps, where the app itself must be sandboxed. If the app wants to install a launchd agent, that agent must also be sandboxed. However, there are actually four combinations, of which three are supported:
App Sandboxed | Job Sandboxed | Supported
------------- | ------------- | ---------
no | no | yes
no | yes | yes
yes | no | no [1]
yes | yes | yes
There are also two ways to sandbox the job:
Continue to use a macOS > Command Line Tool target for the launchd job.
Use an macOS > App target for the launchd job.
In the first approach you have to use some low-level build settings to enable the App Sandbox. Specifically, you must assign the program a bundle ID and then embed an Info.plist into the executable via the Create Info.plist Section in Binary build setting.
In the second approach you can use the standard Signing & Capabilities editor to give the job a bundle ID and enable the App Sandbox, but you have to adjust the BundleProgram property to account for the app-like wrapper.
IMPORTANT The second approach is required if your launchd job uses restricted entitlements, that is, entitlements that must be authorised by a provisioning profile. In that case you need an app-like wrapper to give you a place to store the provisioning profile. For more on this idea, see Signing a daemon with a restricted entitlement.
For more background on how provisioning profiles authorise the use of entitlements, see TN3125 Inside Code Signing: Provisioning Profiles.
On balance, the second approach is the probably the best option for most developers.
[1] When SMAppService was introduced it was possible to install a non-sandboxed daemon from a sandboxed app. That option is blocked by macOS 14.2 and later.
In macOS 26 I noticed there is a section Menu Bar in System Settings which allows to toggle visibility of status items created with NSStatusItem. I'm assuming this is new, since I never noticed it before.
Currently my app has a menu item that allows toggling its status item, but now I wonder whether it should always create the status item and let the user control its visibility from System Settings. Theoretically, keeping this option inside the app could lead to confusion if the user has previously disabled the status item in System Settings, then perhaps forgot about it, and then tries to enable it inside the app, but apparently nothing happens because System Settings overrides the app setting. Should I remove the option inside the app?
This also makes me think of login items, which can be managed both in System Settings and inside the app via SMAppService. Some users ask why my app doesn't have a launch at login option, and I tell them that System Settings already offers that functionality. Since there is SMAppService I could offer an option inside the app that is kept in sync with System Settings, but I prefer to avoid duplicating functionality, particularly if it's something that is changed once by the user and then rarely (if ever) changed afterwards. But I wonder: why can login items be controlled by an app, and the status item cannot (at least I'm not aware of an API that allows to change the option in System Settings)? If the status item can be overridden in System Settings, why do login items behave differently?
To gain exclusive access to keyboard HID devices like Amazon Fire Bluetooth remote controls, my app has been installing a privileged helper tool with SMJobBless in the past. The app - which also has Accessibility permissions - then invoked and communicated with that helper tool through XPC.
Now I'm looking into replacing that with a daemon installed through the newer SMAppService APIs, but running into a permission problem:
If I try to exclusively open a keyboard HID device from the SMAppService-registered XPC service/daemon (which runs as root as seen in Activity Monitor), IOHIDDeviceOpen returns kIOReturnNotPermitted.
I've spent many hours now trying to get it to work, but so far didn't find a solution.
Could it be that XPC services registered as a daemon through SMAppService do not inherit the TCC permissions from the invoking process (here: Accessibility permissions) - and the exclusive IOHIDDeviceOpen therefore fails?
I am trying to get a followup on the following thread.
It seems like it is not possible to prevent non-admin users from unloading launch agents through the terminal or deleting the user level ones.
We want to keep our Mac UI app running all the time, when a user is logged into to a mac machine (app resides in /Applications). To achieve this, we can use launchctl from within post-isntall script to load a plist file which resides in /Library/LaunchAgent.
How to prevent a user (without admin password) to unload the agent using launchctl from terminal?
I'm working on an enterprise product that's mainly a daemon (with Endpoint Security) without any GUI component. I'm looking into the update process for daemons/agents that was introduced with Ventura (Link), but I have to say that the entire process is just deeply unfun. Really can't stress this enough how unfun.
Anyway...
The product bundle now contains a dedicated Swift executable that calls SMAppService.register for both the daemon and agent.
It registers the app in the system preferences login items menu, but I also get an error.
Error registering daemon: Error Domain=SMAppServiceErrorDomain Code=1 "Operation not permitted" UserInfo={NSLocalizedFailureReason=Operation not permitted}
What could be the reason?
I wouldn't need to activate the items, I just need them to be added to the list, so that I can control them via launchctl.
Which leads me to my next question, how can I control bundled daemons/agents via launchctl? I tried to use launchctl enable and bootstrap, just like I do with daemons under /Library/LaunchDaemons, but all I get is
sudo launchctl enable system/com.identifier.daemon
sudo launchctl bootstrap /Path/to/daemon/launchdplist/inside/bundle/Library/LaunchDaemons/com.blub.plist
Bootstrap failed: 5: Input/output error (not super helpful error message)
I'm really frustrated by the complexity of this process and all of its pitfalls.
Hi,
We have a macOS application that contains a helper daemon that was registered with launchd using the SMAppService API and for the most part its been working okay until we tried to release an update that added an XPC service to the daemon. When users try to upgrade the software, the new service now fails to launch due to a launch constraint violation.
The Console log shows the following error after the upgrade:
AMFI: Launch Constraint Violation (enforcing), error info: c[5]p[1]m[1]e[0], (Constraint not matched) launching proc[vc: 6 pid: 1422]: /Applications/Mozilla VPN.app/Contents/Library/LaunchServices/org.mozilla.macos.FirefoxVPN.daemon, launch type 0, failure proc [vc: 6 pid: 1422]: /Applications/Mozilla VPN.app/Contents/Library/LaunchServices/org.mozilla.macos.FirefoxVPN.daemon
The service plist before the upgrade looked like this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AssociatedBundleIdentifiers</key>
<string>org.mozilla.macos.FirefoxVPN</string>
<key>Label</key>
<string>org.mozilla.macos.FirefoxVPN.service</string>
<key>BundleProgram</key>
<string>Contents/MacOS/Mozilla VPN</string>
<key>ProgramArguments</key>
<array>
<string>Mozilla VPN</string>
<string>macosdaemon</string>
</array>
<key>UserName</key>
<string>root</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>SoftResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>1024</integer>
</dict>
<key>StandardErrorPath</key>
<string>/var/log/mozillavpn/stderr.log</string>
</dict>
</plist>
The updated plist changes the BundleProgram, removes ProgramArguments and adds MachServices, which results in the following plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AssociatedBundleIdentifiers</key>
<string>org.mozilla.macos.FirefoxVPN</string>
<key>Label</key>
<string>org.mozilla.macos.FirefoxVPN.service</string>
<key>BundleProgram</key>
<string>Contents/Library/LaunchServices/org.mozilla.macos.FirefoxVPN.daemon</string>
<key>MachServices</key>
<dict>
<key>org.mozilla.macos.FirefoxVPN.service</key>
<true/>
</dict>
<key>UserName</key>
<string>root</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>SoftResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>1024</integer>
</dict>
<key>StandardErrorPath</key>
<string>/var/log/mozillavpn/stderr.log</string>
</dict>
</plist>
On a fresh machine/VM, this works just fine, we only encounter the Launch Constraint Violation when upgrading from one version to the next.
We were hoping that the service could have been upgraded by calling unregisterWithCompletionHandler first, but this seems have no effect on the bug.
So, I guess my questions are:
Is there a way to diagnose what the launch constraints are for a service, and which why the constraints are being violated?
How does one go about changing the plist for a daemon installed via SMAppService?
Thanks,
Naomi
Hello, I am currently researching to develop an application where I want to apply the MacOS updates without the password prompt shown to the users.
I did some research on this and understand that an MDM solution can apply these patches without user intervention.
Are there any other ways we can achieve this? Any leads are much appreciated.
Hi, I'm working on an application on MacOS. It contains a port-forward feature on TCP protocol.
This application has no UI, but a local HTTP server where user can access to configure this application.
I found that my application always exit for unknown purpose after running in backgruond for minutes. I think this is about MacOS's background process controlling.
Source codes and PKG installers are here: https://github.com/burningtnt/Terracotta/actions/runs/16494390417
I've discovered that a system network extension can communicate with a LaunchDaemon (loaded using SMAppService) over XPC, provided that the XPC service name begins with the team ID.
If I move the launchd daemon plist to Contents/Library/LaunchAgents and swap the SMAppService.daemon calls to SMAppService.agent calls, and remove the .privileged option to NSXPCConnection, the system extension receives "Couldn't communicate with a helper application" as an error when trying to reach the LaunchAgent advertised service. Is this limitation by design?
I imagine it is, but wanted to check before I spent any more time on it.
Topic:
App & System Services
SubTopic:
Processes & Concurrency
Tags:
Service Management
XPC
System Extensions
Network Extension
My app uses SMAppService to register a privileged helper, the helper registers without errors, and can be seen in System Settings. I can get a connection to the service and a remote object proxy, but the helper process cannot be found in Activity Monitor and the calls to the proxy functions seem to always fail without showing any specific errors. What could be causing this situation?
SMAppService Error 108 "Unable to read plist" on macOS 15 Sequoia - Comprehensive Test Case
Summary
We have a fully notarized SMAppService implementation that consistently fails with Error 108 "Unable to
read plist" on macOS 15 Sequoia, despite meeting all documented requirements. After systematic testing
including AI-assisted analysis, we've eliminated all common causes and created a comprehensive test
case.
Error: SMAppServiceErrorDomain Code=108 "Unable to read plist: com.keypath.helperpoc.helper"
📋 Complete Repository: https://github.com/malpern/privileged_helper_help
What We've Systematically Verified ✅
Perfect bundle structure: Helper at Contents/MacOS/, plist at Contents/Library/LaunchDaemons/
Correct SMAuthorizedClients: Embedded in helper binary via CREATE_INFOPLIST_SECTION_IN_BINARY=YES
Aligned identifiers: Main app, helper, and plist all use consistent naming
Production signing: Developer ID certificates with full Apple notarization and stapling
BundleProgram paths: Tested both Contents/MacOS/helperpoc-helper and simplified helperpoc-helper
Entitlements: Tested with and without com.apple.developer.service-management.managed-by-main-app
What Makes This Different
Systematic methodology: Not a "help me debug" post - we've done comprehensive testing
Expert validation: AI analysis helped eliminate logical hypotheses
Reproduction case: Minimal project that demonstrates the issue consistently
Complete documentation: All testing steps, configurations, and results documented
Use Case Context
We're building a keyboard remapper that integrates with https://github.com/jtroo/kanata and needs
privileged daemon registration for system-wide keyboard event interception.
Key Questions
Does anyone have a working SMAppService implementation on macOS 15 Sequoia?
Are there undocumented macOS 15 requirements we're missing?
Is Error 108 a known issue with specific workarounds?
Our hypothesis: This appears to be a macOS 15 system-level issue rather than configuration error, since
our implementation meets all documented Apple requirements but fails consistently.
Has anyone encountered similar SMAppService issues on macOS 15, or can confirm a working
implementation?
Topic:
App & System Services
SubTopic:
Processes & Concurrency
Tags:
Service Management
Notarization
My app is for personal use currently, so distribution won't be a problem. It registers a privileged helper using SMAppService, and I was wondering whether there is a way to customize the authorization dialog that the system presents to the user.
I am developing the application in Mac. My requirement is to start the application automatically when user login.
I have tried adding the plist file in launch agents, But it doesn't achieve my requirement.
Please find the code added in the launch agents
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.sftk.secure</string>
<key>ProgramArguments</key>
<array>
<string>/Applications/Testing.app/Contents/MacOS/Testing</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<false/>
</dict>
</plist>
I have tried by adding manually in the setting, but it was opened sometimes and closed suddenly. On open manually it works.
Please provide a solution to start the application automatically on system starts
our app has a helper to perform privileged operations.
previously that helper was installed via SMJobBless() into the /Library/LaunchDaemons/ and /Library/PrivilegedHelperTools/
we also had a script that would install the helper from the command-line, which was essential for enterprise users that could not manually install the helper on all their employee's Macs. the script would copy the files to their install location and would use launchctl bootstrap system as the CLI alternative to SMJobBless(). the full script is here: https://pastebin.com/FkzuAWwV
due to various issues with the old SMJobBless() approach we have ported to helper to the new SMAppService API where the helpers do not need to be installed but remain within the app bundle ( [[SMAppService daemonServiceWithPlistName:HELPER_PLIST_NAME] registerAndReturnError:&err] )
however, we are having trouble writing a (remote-capable) CLI script to bootstrap the new helper for those users that need to install the helper on many Macs at once. running the trivial
sudo launchctl bootstrap system /Applications/MacUpdater.app/Contents/Library/LaunchDaemons/com.corecode.MacUpdaterPrivilegedInstallHelperTool2.plist
would just result in a non-informative:
Bootstrap failed: 5: Input/output error
various other tries with launchctl bootstrap/kickstart/enable yielded nothing promising.
so, whats the command-line way to install a SMAppService based helper daemon? obviously 'installing' means both 'registering' (which we do with registerAndReturnError in the GUI app) and 'approving' (which a GUI user needs to manually do by clicking on the notification or by going into System Settings).
thanks in advance!
p.s. we wanted to submit this as a DTS TSI, but those are no longer available without spending another day on a reduced sample projects. words fail me.
p.p.s. bonus points for a CLI way to give FDA permissions to the app!
Topic:
App & System Services
SubTopic:
Core OS
Tags:
Entitlements
Service Management
Command Line Tools
Hello,
I want to create a Launch Agent that triggers an executable upon changes in the /Applications folder.
The launch agent is normally a loaded but not running, and by adding /Applications to the WatchPath parameters in the plist, launchd is supposed to trigger the process, that will run and exit once done.
Sadly this seems not to be working uniformly. The script only works on one machine, in the the others the execcutable is never run. There seem not to be any meaningful differences in the launchd or system logs.
The same identical plist works perfectly when changing something in the user's ~/Applications folder. The script does its job and logs are visible.
Is there an undocumented limitation specifically for the /Applications folder that prevents luanchd to observe it in the WatchPaths? Maybe SIP not allowing access? But why does it work on my machine?
Here is an example of the ~/Library/LaunchAgents/com.company.AppName.LaunchAgent.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AssociatedBundleIdentifiers</key>
<string>com.company.AppName</string>
<key>KeepAlive</key>
<false/>
<key>Label</key>
<string>com.company.AppName.LaunchAgent</string>
<key>ProgramArguments</key>
<array>
<string>/Users/username/Library/Application Support/com.company.AppName/Launch Agent.app/Contents/MacOS/LaunchAgent</string>
</array>
<key>RunAtLoad</key>
<false/>
<key>WatchPaths</key>
<array>
<string>/Users/username/Applications</string>
<string>/Applications</string>
<string>/Network/Applications</string>
</array>
</dict>
</plist>
With the executable being a standard app bundle in /Users/username/Library/Application Support/com.company.AppName/Launch Agent.app
Thank you