"A single probe, from one host, at one moment, is evidence about the probe."
One User, Three Contexts
When you log in at a Mac's screen, macOS builds an Aqua session around you: the window server, a login keychain that can show a prompt, and the gui/<uid> launchd domain. An ssh login as the same user gets the same files and a different security session. A launchd job is a third context again, with its own environment. Fleet tooling that forgets which one it is in produces errors that look like bugs in the app.
What an ssh Session Can and Cannot Do
Measured from ssh on a Mac whose owner was logged in at the console:
- The login keychain answers "User interaction is not allowed", so
codesignwith any identity stored there fails with the opaqueerrSecInternalComponent. That includes the self-signed identity and the Apple Development identity an iOS archive needs. launchctl asuserfails with "Could not switch to audit session … Operation not permitted". Switching sessions needs root.- Signals work normally, and a direct Apple Event to an app (
tell application "Spark" to quit) worked, while anything through System Events hung until error -1712. Runosascriptin the background with a deadline, because a quit blocked by an unsaved-changes dialog never returns.
open is the case to hold loosely. A July probe on one peer returned exit 0 and launched nothing. A September probe on another Mac launched an AppKit app that reached applicationDidFinishLaunching. The two measurements disagree, so neither is a law. What both prove is that the exit status says nothing: check for the process.
Doing GUI-Session Work From ssh
The dependable bridge is a one-shot LaunchAgent bootstrapped into gui/<uid>. The job runs inside the Aqua session, so the keychain works and signing succeeds, with no window. The family's iOS archive runs this way from remote sessions. The tempting alternative, telling Terminal to run a script through AppleScript, typed the path into whichever tab was in front, which was the owner's live system monitor. Never drive a person's apps to reach their session.
The bridge needs a console user. At the login window the GUI domain does not exist, and launchctl bootstrap gui/<uid> fails with 125: Domain does not support specified action. For an always-on agent that is fine: its plist in ~/Library/LaunchAgents is the durable activation, and RunAtLoad starts it at the next login. Report staged instead of failing. A one-shot job has no next login to wait for, so for it the same missing console is a failure it names.
A launchd Job Is Not Your Shell
A job's PATH is launchd's /usr/bin:/bin:/usr/sbin:/sbin unless its plist sets one, so Homebrew tools are not found. A plist that sets PATH to add Homebrew can drop /usr/sbin, which is how an engine's lsof probe reported every port as having no listener. Local network privacy differs too. Tools run from Terminal or over ssh are allowed automatically, launchd agents are not. An engine running as an agent got "No route to host" for a LAN hostname that the same user's ssh shell reached.