Mac has recovered network access, but the GitLab Runner still appears offline in GitLab.

Fastest fix: treat the failure as a missing user session first. GitLab Runner on macOS is supported as a user-level LaunchAgent, so do not replace it with a LaunchDaemon. Check the CI account, FileVault unlock path, user session, Keychain access, and Runner service state before reinstalling anything.

This guide is for enterprise IT teams that need a remote Mac to return to service without waiting for someone to log in locally.

It is also for engineering productivity owners separating a Runner failure from a routing problem, and for security or release owners defining an auditable boundary between automatic recovery, FileVault, and signing credentials.

01

The four states that look like one outage

A restarted Mac can be reachable over the network while the build node is still unusable. We separate these states before changing the installation:

  1. Host booted — the operating system has started.
  2. Storage unlocked — the startup volume is available after FileVault authentication.
  3. User session established — the dedicated CI account has logged in.
  4. Runner loaded — the user-level LaunchAgent has started and can contact GitLab.

These states are related but not interchangeable. A successful SSH connection proves network access. It does not prove that the CI account has an active graphical session, that the LaunchAgent loaded under that account, or that the signing Keychain is available.

The supported macOS installation model is the starting point. GitLab documents the Runner service as a user-level launch agent on macOS, not as a system daemon. The GitLab macOS installation documentation should be checked against the actual account and service state before any reinstall.

This week’s action: run one controlled restart on a non-production node and record four timestamps: host reachable, disk unlocked, CI user session available, and first successful test job.

That record tells us whether the failure is a boot problem, an authentication problem, a user-session problem, or a Runner problem.

02

First check: is the Runner missing, or is the job routed elsewhere?

A GitLab page showing “offline” can reflect more than one failure. A Runner may be connected but unable to receive a particular job because its tags, protection settings, or project scope do not match the pipeline.

Start with the Runner registration and service context. On the Mac, inspect the account that owns the Runner installation:

whoami
id
gitlab-runner status
gitlab-runner verify

Run these commands as the dedicated CI account, not as an administrator account used for emergency maintenance. A second installation under an administrator profile can create a separate configuration file, service definition, or Runner identity. The result is often a misleading situation: one account has a healthy process while the account assigned to builds has no loaded service.

Then inspect the user-level launch items:

launchctl print "gui/$(id -u)"
launchctl list | grep -i gitlab

The exact output varies by macOS release and installation state. The useful evidence is the account identifier, service label, process state, and recent error message. Do not interpret an empty result from an administrator shell as proof that the Runner is absent. A user agent belongs to the graphical session of its owner.

Also review GitLab-side routing. Confirm that:

  • the Runner is assigned to the intended project or group;
  • the job tags match the Runner tags;
  • protected branch and protected tag rules allow the Runner to accept the job;
  • the Runner is not paused or restricted;
  • the pipeline is not requesting a capability missing from the node.

GitLab’s Runner configuration and tag guidance explains why a connected Runner can still be excluded from a job. This is a routing diagnosis, not a macOS restart diagnosis.

03

Why does GitLab Runner fail to start automatically after a Mac restart?

The usual cause is a state mismatch: the Mac has booted, but the account that owns the LaunchAgent has not established the required user session.

A LaunchAgent is attached to a user context. Apple describes the distinction between user agents and system daemons in its launchd service model. Apple also separates user agents from system daemons by execution context and available privileges in its daemon design documentation.

This creates three common failure patterns:

  • The Runner was installed while logged in as the wrong account.
  • The CI account has no session after reboot.
  • The service was loaded in one user context but checked from another.

Check the service definition and configuration ownership. The relevant files should belong to the dedicated CI account and live in that account’s expected user directories. Do not copy a working plist into a system directory merely to make it start earlier.

Why the CI account matters

The account is not only a process owner. It determines access to:

  • the login Keychain;
  • Xcode signing identities;
  • simulator data;
  • derived data and dependency caches;
  • project files and SSH credentials;
  • graphical session services.

A Runner process under the wrong account may appear online while a real Xcode job fails later. This is why reinstalling as an administrator often makes the diagnosis worse. It changes ownership and hides the original user-session problem.

Operational warning: Do not convert the macOS Runner into a LaunchDaemon as a shortcut for unattended startup. That changes the supported execution context and can remove the user-session access needed by Xcode, simulators, login Keychain items, and signing workflows.

If the organization requires a system-level service, treat that as a separate architecture decision. Do not describe it as the same GitLab-supported macOS service mode.

04

LaunchAgent or LaunchDaemon: which recovery path is valid?

The difference is operational, not cosmetic.

Area User-level LaunchAgent System-level LaunchDaemon
Execution context Dedicated logged-in CI user System context
GitLab macOS service model Supported installation direction Not the documented default
Login Keychain access Can use the user session when configured correctly Often unavailable or different
Simulator and GUI-dependent tasks Better aligned with user session requirements May lack required session context
Typical risk Fails when the CI user never logs in Runs without the credentials and UI context builds need
Recommended use here Preserve and repair Do not substitute during incident response

The critical point is not that a LaunchDaemon can never execute a process. The point is that replacing the documented user-level model changes the assumptions behind the build environment.

The first repair sequence should be:

  • [ ] Confirm the dedicated CI account name.
  • [ ] Confirm the Runner configuration belongs to that account.
  • [ ] Confirm the LaunchAgent is in the account’s user context.
  • [ ] Establish or restore the CI user session.
  • [ ] Check the service with launchctl as that user.
  • [ ] Run gitlab-runner verify as that user.
  • [ ] Submit a small non-release job.
  • [ ] Keep the service in LaunchAgent mode unless the architecture has been separately approved.

This sequence answers the practical question of whether a macOS GitLab Runner must be logged in. For the supported user-level model, the user session is a functional dependency. A reachable host without that session is not a ready build node.

05

FileVault recovery versus unattended login

FileVault is where convenience and protection become an explicit trade-off.

An organization cannot assume that encrypted storage and automatic login will both work as default recovery mechanisms. Apple states that automatic login is unavailable when FileVault is enabled in the normal protected configuration. The Apple automatic login guidance should be checked against the Mac’s actual policy.

The recovery design must answer four questions:

  1. Who or what unlocks the startup volume?
  2. Is remote access available before the target user session starts?
  3. Which account establishes the CI session after unlock?
  4. Where is the evidence that the recovery action was authorized?

Apple’s FileVault management documentation describes the security boundary around volume unlock and recovery credentials. The correct design depends on the Mac hardware, macOS release, device management controls, network path, and the organization’s security policy. We do not treat remote recovery as guaranteed without testing that exact combination.

For a remote CI node, evaluate these options separately:

  • Controlled operator unlock: an authorized operator unlocks the Mac through an approved remote path, with an audit record.
  • Recovery account workflow: a restricted recovery identity is used under documented procedures.
  • Physical or out-of-band intervention: required when the Mac cannot expose a trusted pre-login recovery path.
  • Dedicated standby capacity: used when the release process cannot wait for manual recovery.

Do not store FileVault recovery material in the same place as signing credentials. Use separate ownership, access rules, and audit trails. FileVault unlock proves that storage is available. It does not prove that the CI account logged in or that the Runner can sign an application.

06

Runner online, but Keychain or simulator access still fails

A green Runner status is only a process-level signal. It does not certify a production build.

The next check should use progressively stronger tests:

Test layer What it proves Typical failure boundary
Shell echo and environment read Runner can accept and execute a job Registration, routing, or shell setup
Xcode project configuration check Xcode and project files are available Account, path, or toolchain setup
Simulator boot and test launch User session and simulator context work GUI session, permissions, or runtime state
Code-signing test Required identity and Keychain access work Login Keychain, access control, or signing profile
Non-release archive Build path is operational without publishing Dependency, signing, or artifact configuration

Keep these tests separate. A full release pipeline can fail in several places and conceal the first broken dependency.

The most important distinction is between:

  • the login Keychain of the CI account;
  • the system Keychain;
  • the account running the Runner;
  • the signing identity used by the build;
  • the permissions granted to the CI process.

If the Runner reconnects but signing fails, do not reinstall the Runner immediately. Check whether the process has the same user identity and session as before the reboot. Check whether the signing identity is in the expected Keychain. Check whether the Keychain is locked or requires an interactive approval that the CI process cannot provide.

Use a small build that fails safely and produces no release artifact. Record the command, account, exit code, and relevant log location. Avoid dumping the entire environment into logs. GitLab’s Runner troubleshooting guidance warns against exposing variables and credentials while debugging.

07

Second check: remove duplicate services and false identities

A common incident pattern begins with an emergency reinstall. An operator runs the installer under a different account, registers a new Runner, and leaves the old LaunchAgent in place.

The result can include:

  • multiple configuration files;
  • more than one LaunchAgent label;
  • different system_id values;
  • stale project or group registrations;
  • a healthy process under an account that no pipeline uses;
  • confusing logs from old and new services.

Build an inventory before deleting anything:

ps aux | grep -i '[g]itlab-runner'
find "$HOME/Library/LaunchAgents" -maxdepth 1 -type f -iname '*gitlab*' -print
gitlab-runner list
gitlab-runner verify

Run the inventory under the intended CI account and under the administrator account only when necessary. Compare file ownership, configuration paths, registration scope, tags, and timestamps.

Do not remove a service during a production release window unless the replacement path is already tested. First disable job intake or drain the node according to the organization’s release procedure. Then preserve the old configuration and logs for audit. Delete only after the active identity is confirmed.

Debug logging should be temporary. Enable it in a controlled window, restrict access to the logs, and disable it after the evidence is collected. Runner variables may contain tokens, deployment credentials, or signing-related information.

08

A restart acceptance runbook for production admission

A node is not ready because its status changed to online. It is ready when it completes the same class of work that production requires.

Use this acceptance sequence:

Step 1: record the baseline

Before restarting, capture:

  • current Runner status;
  • CI account name;
  • service label and configuration path;
  • assigned tags and project scope;
  • Xcode version and simulator runtime used by the pipeline;
  • signing identity availability;
  • last successful test job.

The configuration record should be stored with the node’s operational documentation, not only in an engineer’s terminal history.

Step 2: perform a planned restart

Use the organization’s approved remote control path. Record the restart request time and the operator identity. Do not infer recovery from a single ping or SSH connection.

Step 3: verify disk and session state

Confirm that the startup volume is unlocked. Then confirm that the dedicated CI account has an active session. If FileVault requires an operator action, record the action and its authorization.

Step 4: verify LaunchAgent and Runner state

As the CI account, inspect the launch service and run:

gitlab-runner status
gitlab-runner verify

Record the result. A successful service command is evidence of process state, not yet evidence of a working build.

Step 5: run layered build tests

Execute the shell test, Xcode configuration check, simulator test, signing test, and non-release archive in that order. Stop at the first failure and classify it. Do not run the full release pipeline to “see whether it works.”

Step 6: test routing

Submit a real test job with the same relevant tags and protected-branch conditions used in production. Confirm that the intended Runner accepts the job and that no duplicate Runner receives it.

Step 7: test failure recovery

Repeat the validation after an abnormal shutdown and after removing the CI account’s access according to the approved access-control test plan. This exposes recovery assumptions that a clean restart will not reveal.

The acceptance result should contain status, evidence, owner, and failed-step action. “Runner online” is not an acceptable final evidence field by itself.

09

When one Mac is not enough

A single Mac may be adequate for development or low-frequency builds. It becomes a release risk when recovery depends on a person being available to unlock storage, restore a user session, repair a damaged LaunchAgent, or clean up a duplicate registration.

Assess a dedicated standby node when:

  • the release process has a recovery deadline shorter than manual intervention allows;
  • FileVault policy prevents unattended recovery;
  • the main node runs multiple incompatible Xcode environments;
  • signing credentials must remain isolated;
  • a restart test has failed more than one acceptance step;
  • one node is also serving interactive development work.

For teams using a remotely managed Mac, compare the current setup against an isolated backup node rather than assuming that a larger primary node solves the recovery problem. A separate remote Mac can provide a clean Runner identity, a separately audited CI account, and an independently tested recovery path.

MESHLAUNCH can be considered only after the acceptance requirements are written down. Review the available remote Mac options against the required access method, account model, build isolation, and replacement procedure. Regional availability should be checked from the relevant Mac infrastructure options, not assumed from a generic architecture diagram.

10

The decision checklist

Use this before returning a restarted node to production:

  • [ ] The Mac is reachable through the approved remote path.
  • [ ] FileVault status and unlock ownership are documented.
  • [ ] The dedicated CI account is confirmed.
  • [ ] The account has an active user session.
  • [ ] The Runner is installed under the intended account.
  • [ ] The LaunchAgent is loaded in that account’s context.
  • [ ] No unauthorized LaunchDaemon replacement is present.
  • [ ] No duplicate Runner registration is accepting jobs.
  • [ ] Tags and protected-branch rules match the production pipeline.
  • [ ] A shell job completes.
  • [ ] An Xcode configuration check completes.
  • [ ] A simulator test completes where required.
  • [ ] A code-signing test can access the intended identity.
  • [ ] A non-release archive completes.
  • [ ] Debug logs contain no exposed tokens or signing secrets.
  • [ ] Planned restart recovery has been recorded.
  • [ ] Abnormal shutdown recovery has been tested.
  • [ ] The failure owner and fallback node are documented.

If any item fails, keep the node out of release traffic or route only the jobs proven to work. Do not use the GitLab status page as the only admission signal.

The current approach—one self-managed Mac with recovery dependent on a local login, manual FileVault action, and informal Runner cleanup—has three clear weaknesses: recovery time depends on staff availability, a green Runner process can hide unusable Keychain or simulator state, and duplicate service registrations can create silent job-routing errors. Renting an isolated remote Mac through MESHLAUNCH can provide a more controlled second environment for testing, standby capacity, or temporary release work, but it should enter production only after the same restart and build acceptance checks pass.

For teams that still need to validate the access model, start with the MESHLAUNCH remote Mac environment, document the CI account and recovery boundary, and test one complete restart-to-build cycle before moving release traffic.