The Mac build machine is reachable over SSH after a reboot, but the signing pipeline is still offline.

The fastest fix is usually a two-layer design: use LaunchDaemon for host recovery and health checks, then run the CI Agent as a dedicated service account’s LaunchAgent when Keychain, code signing, or iOS Simulator access is required.

This guide is for:

  • IT owners responsible for unattended Mac build nodes.
  • Platform engineers managing iOS signing, simulator testing, and CI Agents.
  • Technical directors evaluating remote Mac capacity, recovery evidence, and security isolation.
01

Start with the recovery target, not the plist

“Booted” is not the same state as “ready to build.” An enterprise Mac build machine passes through several operational conditions:

  • The operating system has started.
  • The storage volume is available.
  • A user session exists.
  • The CI Agent is registered.
  • The signing Keychain is usable.
  • A real build or test job succeeds.

A monitoring dashboard that reports the host as online proves only part of this chain. A reliable acceptance design must test the final production outcome, not just process presence.

Apple’s launchd documentation separates system-context services from user-session processes. A LaunchDaemon runs in the system context, while a LaunchAgent runs in a user context. The distinction is defined in Apple’s launchd job model, not by the CI platform’s product naming.

Operational state What it proves What it does not prove
Host responds to SSH Network and remote access are available A user session or CI Agent exists
LaunchDaemon is loaded A system service has been registered User Keychain access works
LaunchAgent is loaded A user-context process has started Signing credentials or Simulator services work
CI Agent is online The coordinator can route work The selected signing identity is usable
A signed build passes The production path works Recovery will work after every restart scenario

This separation should be written into the node’s service-level requirements. “Automatically starts after reboot” needs a precise definition: does it mean host access, agent registration, or a successful signed build?

02

Choose the launchd context by dependency

A LaunchDaemon is appropriate for host-level work that does not need a graphical login session or a user’s protected credentials. Examples include a health probe, a local watchdog, a controlled service-state check, or a recovery signal sent to an external operations system.

A LaunchAgent is tied to a user session. That makes it a better candidate for a CI Agent that must use a login Keychain, a user-owned workspace, an iOS Simulator, or other services exposed inside the session. The correct service mode still depends on the specific CI Agent. It should be checked against that tool’s official macOS installation guidance rather than assumed from a generic launchd example.

GitLab’s official macOS Runner documentation is a useful boundary case: its supported macOS service mode is user-mode LaunchAgent. That fact should not be generalized into a universal rule for Jenkins, TeamCity, or another CI product. The platform owner must verify the selected Agent’s documented service model in the same way.

Mode Identity Best fit Main rejection condition
LaunchDaemon System context Host checks, recovery helpers, non-secret monitoring The process must access a user Keychain, Simulator, or login session
Global LaunchAgent A user context configured for broader availability Centrally managed user-session services The account, login policy, or session lifecycle is not controlled
User-level LaunchAgent Dedicated CI service account Signing, Simulator tests, user workspace, CI Agent runtime No reliable way exists to establish and protect the required session

How should a Mac build machine start its CI Agent after a reboot?
If the Agent only performs host-level work, a LaunchDaemon may be sufficient. If it performs code signing, reads a temporary Keychain, or drives iOS Simulator tests, place it in a dedicated service account’s LaunchAgent and make the login-session requirement explicit. The system layer can report that the host is ready, but it should not impersonate user readiness.

A minimal user-level service might contain only the runtime path, the service label, and the account-specific working directory. The exact executable and arguments must come from the CI platform’s official installer:

<?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.example.ci-agent</string>
  <key>ProgramArguments</key>
  <array>
    <string>/path/to/ci-agent</string>
    <string>run</string>
  </array>
  <key>WorkingDirectory</key>
  <string>/Users/ci-agent/work</string>
</dict>
</plist>

This fragment is intentionally incomplete. It does not prescribe a product-specific executable, secret, KeepAlive policy, or permission model. Those values need review because an incorrect account or automatic restart policy can turn a configuration mistake into a persistent failure loop.

A common diagnostic sequence is:

launchctl print system/com.example.host-check
launchctl print gui/$(id -u)/com.example.ci-agent
pgrep -alf ci-agent

The first command checks the system domain. The second checks the graphical user domain. The third only confirms a process match; it does not confirm Agent registration, Keychain access, or a successful build.

03

Keychain and Simulator access define the user boundary

Code signing is not simply a file permission problem. The build must locate the intended signing identity, access the correct private key, and use a security policy that permits the build process to perform the required operation. Apple’s Keychain guidance for macOS should be read alongside the organization’s credential-handling policy.

A LaunchDaemon often fails here because its system context is not the same as the dedicated CI user’s login context. Moving the daemon to root can make a path readable, but it does not automatically provide the correct user Keychain, approval state, workspace ownership, or Simulator session. It can also expand the impact of a compromised build step.

Why can a LaunchDaemon fail to access the Keychain?
The process may be running outside the user session that owns the Keychain, using a different home directory, or lacking the access policy associated with the signing item. The failure should be diagnosed by checking the effective user, home directory, Keychain search path, and security policy. It should not be “fixed” by granting broad permissions or by moving the entire build process to root.

Apple’s documentation on creating distribution-signed code provides the signing context, but it does not eliminate the need to test the actual CI account. A successful interactive build under an administrator account is not evidence that the unattended Agent can sign.

The same boundary applies to iOS Simulator work. Simulator processes, user directories, derived data, and GUI-related services can depend on the logged-in account. A build that compiles from a system service may still fail when tests require a session-owned simulator device.

A suitable enterprise split is:

  • System recovery layer: confirms network reachability, disk state, Agent service health, and approved recovery actions.
  • User build layer: runs the CI Agent, uses the dedicated workspace, accesses approved signing assets, and executes controlled builds.
  • CI coordinator: records registration, job acceptance, build result, and failure reason independently from host monitoring.

A root process is not a substitute for a valid user session. If the production job needs the user session, preserve that dependency and monitor it explicitly.

04

FileVault recovery needs a separate acceptance test

What should happen to an unattended Mac when FileVault is enabled?
The node should be treated as unavailable for production builds until its approved disk-unlock and user-session path has completed. Whether that path can be automated depends on the organization’s FileVault policy, physical access controls, recovery procedures, and remote-management design. It should not be assumed from a successful normal restart.

Apple’s FileVault security documentation explains the security model and recovery implications. The operational question for an IT team is narrower: after the required unlock event, can the node establish the expected user session and complete a signed build without manual repair?

Test these recovery paths separately:

  • A normal planned restart.
  • An unexpected power interruption.
  • A restart initiated by remote operations.
  • A user logout followed by the approved login process.
  • A FileVault-protected restart requiring the organization’s unlock procedure.
  • An Agent process failure after the user session is already active.

Each test should capture timestamps from host reachability, user-session availability, Agent registration, Keychain verification, and build completion. The evidence should be retained with the node’s change record.

Apple’s support guidance on safe Mac restart and startup behavior can support the operational runbook, but it cannot replace a production build test. A control-plane status of “online” is not enough.

05

Security decisions belong to the service account design

The practical comparison is not only LaunchDaemon versus LaunchAgent. It is also root versus a dedicated CI account versus a shared administrator account.

A root build process can reach more of the host, which increases the possible impact of malicious build input, dependency compromise, or a leaked CI token. A shared administrator account creates a different problem: it weakens attribution and makes it harder to prove which workflow accessed a signing asset.

A dedicated CI account should have:

  • A separate home directory and workspace.
  • Only the signing assets required by its assigned pipelines.
  • No routine interactive administrator privilege.
  • Network access restricted according to the build and artifact flow.
  • Logs tied to the Agent identity and node identity.
  • A documented process for rotating certificates, profiles, and tokens.

The system layer should perform actions that do not require release credentials. It may check whether the user-layer Agent is registered, record a failure, or trigger an approved recovery workflow. It should not read signing secrets merely to decide whether the build layer is healthy.

The organization should document every exception where a build step needs elevated access. The record should state the asset, reason, duration, account, approval, and rollback path. This is more useful than a general claim that the node is “locked down.”

06

Observability must distinguish crash, session loss, and routing failure

KeepAlive can restart a process repeatedly while hiding the real cause of failure. A CI Agent may be crashing because of a bad executable path, missing environment variables, an unavailable user session, an invalid credential, or a coordinator-side registration problem. These cases require different owners and different fixes.

At minimum, the node record should include:

  • The launchd domain and service label.
  • The effective account and home directory.
  • Standard output and error log destinations.
  • Last process exit status.
  • Restart history and configuration changes.
  • Agent registration state.
  • Keychain verification result.
  • Last successful signed build.
  • macOS, Xcode, and Agent version changes.

Use launchctl output as evidence of service state, not as a complete health signal:

launchctl print gui/$(id -u)/com.example.ci-agent
launchctl print-disabled gui/$(id -u)
log show --last boot --predicate 'process == "launchd"'

The exact log query may vary with the operating system version and local logging policy. The important requirement is correlation: the operations record should show whether the process never started, started under the wrong account, started without the expected session, registered but rejected work, or accepted work and failed during signing.

System updates, Agent upgrades, plist changes, and credential rotations should first run on an isolated node. A configuration that survives a process restart may still fail after a full restart or a changed login policy. The test result belongs in the change record before the configuration is promoted to the production pool.

07

The architecture decision should use explicit admission gates

The following matrix turns the service-mode choice into a production decision rather than a preference.

Condition LaunchAgent-only LaunchDaemon-only Two-layer design
Host-level health monitoring Limited Strong Strong
User Keychain access Strong when session is valid Not a safe assumption Strong in the build layer
Simulator-dependent tests Suitable when session is valid Usually unsuitable Suitable with explicit session checks
Early boot recovery checks Weak Strong Strong
Separation of recovery and build privileges Limited Limited if it runs builds Strong
Diagnosis of session loss Possible but incomplete Poor for user-state diagnosis Explicit
Production recommendation Conditional Conditional Default for enterprise CI

Choose LaunchAgent-only when the CI platform officially requires user mode, the organization can guarantee the session lifecycle, and host recovery is handled elsewhere. Choose LaunchDaemon-only only when the workload is genuinely system-context work and does not require user Keychain, Simulator, or GUI-dependent behavior.

Choose the two-layer architecture when the node must both recover as a host and run user-dependent production builds. This is the default recommendation for an enterprise iOS CI node because it prevents one service mode from being forced to solve two different problems.

Before approval, complete this admission checklist:

  • [ ] The CI platform’s official macOS service mode has been verified.
  • [ ] The service account, home directory, and workspace are documented.
  • [ ] The build has been tested with the same account used by the Agent.
  • [ ] Keychain access has been verified without moving the build to root.
  • [ ] Code signing has passed in an unattended job.
  • [ ] Simulator tests have passed in the intended user session.
  • [ ] A normal planned restart has reached a successful build.
  • [ ] An unexpected power recovery test has reached a successful build.
  • [ ] The approved FileVault unlock path has been tested.
  • [ ] Remote restart access has been tested without physical intervention.
  • [ ] Agent registration is checked separately from host reachability.
  • [ ] Logs distinguish crash, missing session, credential failure, and routing failure.
  • [ ] A rollback exists for macOS, Xcode, Agent, plist, and credential changes.
  • [ ] A spare node or capacity fallback is defined for production work.
08

Existing node or remote Mac capacity?

The decision should not end with a plist edit. If the current machine cannot provide an independent user session, approved remote restart, protected signing assets, and repeatable recovery evidence, the problem is infrastructure readiness.

A physical Mac may remain the right choice when the team needs local peripherals, long-lived heavy workloads, or direct control of the hardware. It may be a poor fit when several developers need temporary capacity, when the team cannot maintain spare hardware, or when a failed restart requires an on-site operator.

Remote Mac capacity can be evaluated through NodeMini’s managed Mac options when the requirement is a real hosted Mac with remote access and a defined recovery process. The procurement review should still ask for concrete evidence: how remote restarts are performed, how full permissions are delivered, how user sessions are restored, and how a real signing pipeline is validated after recovery.

For regional capacity planning, the NodeMini Mac options for Hong Kong can be compared with the team’s network and build-routing requirements. The correct choice is not the location with the lowest apparent cost; it is the option that satisfies latency, access control, recovery ownership, and spare-capacity requirements.

A shared remote Mac is not automatically a high-availability design. The platform team still needs an independent recovery path, credential separation, a tested failover procedure, and clear ownership of the CI coordinator. Without those controls, moving the same fragile LaunchAgent configuration to another host only changes the location of the failure.

09

The recommended production pattern

For most enterprise iOS and macOS CI nodes, the production baseline is:

  • LaunchDaemon for host-level checks and approved recovery assistance.
  • Dedicated service-account LaunchAgent for the CI Agent.
  • Explicit validation of Keychain, signing, Simulator, and workspace access.
  • Separate monitoring for host availability, user-session readiness, Agent registration, and real build success.
  • Restart and FileVault acceptance tests recorded as operational evidence.
  • Isolated validation for every system, Agent, plist, and credential change.

The current setup should be rejected or redesigned if it reports only SSH availability, runs signing jobs as root without a documented exception, relies on a shared administrator account, or treats KeepAlive as proof of recovery.

If the existing node cannot provide an independent user session, controlled remote restart, and tested spare capacity, a managed remote Mac rental can offer a cleaner operational boundary than continuing to patch an unreliable host. That route is not automatically better for every team, but it is worth evaluating when the cost of manual recovery, failed releases, and unused backup hardware exceeds the rental commitment. For a team that needs temporary build capacity or a separately recoverable CI node, NodeMini’s remote Mac service is the next option to assess against the checklist above.