Your team’s macOS menu bar app needs to launch when a user logs in. On a remote Mac, you turn on “Launch at Login” and the app shows it as enabled. You disconnect the remote desktop client, reconnect, and find that the app never quit. That does not prove the login item works. Test registration, user approval, and launch after a fresh login as separate checkpoints. In particular, do not mistake a remote client disconnect for a macOS logout.
Define the test target and boundaries
This procedure tests a main-app login item registered with SMAppService.mainApp, not a background helper or a system startup service. The app must target macOS 13 or later and call the registration API from its own process. Running a standalone command-line Swift script cannot register the login item on the app’s behalf.
Prepare a separate standard user account for testing, and confirm in advance that you can get back into that account’s graphical desktop. Do not experiment with a work account that is running builds: logging out ends the interactive session and may interrupt processes that depend on it. Save your work and document how you will reconnect before testing.
“Registered,” “approved,” and “launched after login” are three different findings. Each needs its own evidence; one state does not establish the next.
Check the installed app
Test the app bundle you intend to deliver, rather than only running the app from Xcode. Replace the following path with its actual installation location:
APP="/Applications/ExampleApp.app"
test -d "$APP" || { echo "App bundle not found"; exit 1; }
/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' \
"$APP/Contents/Info.plist"
codesign --verify --deep --strict --verbose=2 "$APP"
Record the bundle identifier in the output and confirm that it matches the build under test. If codesign verification fails, fix the app bundle first; do not misdiagnose an app that cannot launch properly as a login-item failure. A valid signature also does not establish user approval or launch after login.
Handle registration state in the app
Connect registration to an explicit switch in the app’s settings instead of calling it unconditionally on every launch. Put the following code in the macOS app target, and have the caller translate the returned status into a message for the user:
import ServiceManagement
@MainActor
func enableLaunchAtLogin() throws -> SMAppService.Status {
let service = SMAppService.mainApp
if service.status == .notRegistered {
try service.register()
}
return service.status
}
Only .enabled should be presented as enabled. For .requiresApproval, direct the user to check System Settings rather than reporting a failure or silently retrying registration. Pass errors through for the UI to handle as well. When the switch is turned off, call try SMAppService.mainApp.unregister() from the app and then read the status again; changing the appearance of the switch is not enough.
Check user approval
Open System Settings under the same test account and inspect the login-items page to see whether the app is listed and whether it needs to be allowed manually. The name of the settings page may vary by macOS version, so follow the interface on the system you are testing. After approval, return to the app and read SMAppService.mainApp.status again. Do not mark automatic launch as verified merely because the app’s name appears in System Settings.
Perform a fresh login
Quit the app normally first and record that it is no longer running. Then use the macOS menu to explicitly log out of the test account and log back into that account’s graphical desktop. Check for the app window, menu bar icon, or another verifiable running state provided by the app. Agree beforehand which observation counts as a successful launch so that an app that crashes immediately after starting is not marked as passing.
| Checkpoint | Evidence to retain |
|---|---|
| Before registration | App version, bundle identifier, test account |
| After registration | Login-item status read by the app |
| After approval | Status in System Settings and status read again by the app |
| After logging back in | Whether the app is running and responds normally |
| After disabling and logging in again | The app did not launch automatically through this login item |
Test the final row after disabling the login item from within the app. If the app also starts through another mechanism, identify that mechanism first; the mere presence of its process does not invalidate the disable test. When testing is complete, re-enable the item if needed and confirm the switch state.
Rule out remote-session false positives
Closing a remote desktop client, briefly losing network access, or locking the screen does not necessarily end the macOS user session. Seeing the old window after reconnecting usually shows only that the session persisted, not that the login item ran. Launching the app over SSH is not a graphical-login test either: an SSH shell has a different environment from the desktop session.
If registration status is correct but the app does not appear after login, check in order: whether you logged into the same user account that registered it, whether you tested the installed bundle rather than another build copy, whether System Settings still requires approval, and whether the app launched but immediately exited. If the app has startup logs, record the event time alongside the time of this login to distinguish “never launched” from “launched and failed.” Do not write access credentials to the logs.
Record the result for the next build
Keep a brief record of the app version, bundle identifier, macOS version, test account, status before and after registration, approval action, result after logging back in, and result of the retest after disabling the item. After every app bundle update, recheck at least the installed bundle’s identity and the result after a fresh login. If the login-item implementation changes, repeat both the registration and disabling paths.
The point of this procedure is not to prove that a particular machine “will always launch the app automatically.” It is to make the behavior of a specific build reproducible and diagnosable. Once graphical login is kept distinct from a remote connection, login-item problems can usually be narrowed down to the app bundle, user approval, or the app’s own startup.
Frequently asked questions
Why does the app not launch even though registration succeeded?
Check whether SMAppService requires user approval, whether you are testing the installed app, and whether you performed a genuine graphical logout and login. Also check whether the app exits immediately.
Does disconnecting remote desktop count as logging out?
No. The macOS user session may remain active after the client disconnects. Explicitly log out of the test account and sign back in to its graphical desktop.
Bring a cloud Mac into your workflow
VPSPush offers remotely accessible, dedicated physical Macs. Choose a location and rental term, then review the access details and final price in your order.