
A host reboot is not a rare edge case — it happens after OS patches, kernel updates, and unplanned power events. If your automation agent does not start automatically at login, every reboot means a silent gap in work until someone notices and manually relaunches it. The fix is a correctly configured launchd user agent with verified restart behaviour and a short acceptance test you can run every time.
This guide gives you the exact plist, a phased recovery procedure, and concrete acceptance checks so you can confirm the agent came back cleanly without staring at logs.
Any Figment Mac can run a launchd user agent that restarts on login — the mechanism is built into macOS. The plan choice affects what the agent can do after it restarts, not whether it restarts. Prices below are entry prices from Figment's Mac hosting page; the physical i3 and M-series plans currently collect configuration requests and the exact hardware is confirmed in the quote.
| Plan | Entry price | What it is | Best fit |
|---|---|---|---|
| Virtual Intel macOS | $29/mo | 2 vCPU, 8 GiB RAM, 80 GiB disk; browser-console access | API-call agents, git operations, file processing, Shortcuts orchestration |
| Physical i3 Mac | $49/mo (request) | Physical Intel Mac | Same workloads on dedicated Intel hardware |
| M-series Mac | from $95/mo (request) | Apple silicon; exact chip/RAM/disk confirmed in quote | Agents that must execute ARM-only binaries or run Xcode 27 |
| Managed Build Mac | $249/mo | One M-series Mac, CI setup, backups, monitoring, 30 minutes monthly support | Teams that want Figment to handle setup and post-maintenance verification |
The virtual Intel macOS plan is a macOS environment — compatible Intel Xcode 26-or-earlier toolchains run on it (see Apple's porting guide) — so a Python, Node, Shortcuts or bash polling agent will work the same way it would on a physical Mac. Choose M-series if the agent has to execute arm64-only tools or Xcode 27.
For an unattended agent, enable auto-login for the agent user account in System Settings → Users & Groups → Automatic login so launchd user agents load after any reboot without someone logging in.
The Apple launchd guide and the launchd.plist(5) manual describe three restart patterns. Pick the one that matches how your agent exits:
| Pattern | Behaviour | Use when |
|---|---|---|
KeepAlive: true | Restart after every exit, clean or not | Long-running process that must never stop |
KeepAlive dict with SuccessfulExit: false | Restart only after a non-zero (crash) exit | Polling loop that exits 0 when the queue is empty |
KeepAlive dict with SuccessfulExit: true | Restart only after a clean exit | Job you want to repeat continuously |
RunAtLoad: true and no KeepAlive | Run once at load, do not restart | One-shot initialisation task |
A polling agent that exits 0 when the queue drains should use SuccessfulExit: false: launchd restarts it on crash, and StartInterval schedules the next poll on its own.
<!-- ~/Library/LaunchAgents/com.yourteam.agent.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>Label</key>
<string>com.yourteam.agent</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/agent/agent-workspace/agent-loop.sh</string>
</array>
<!-- Poll every 90 seconds even after a clean exit -->
<key>StartInterval</key>
<integer>90</integer>
<!-- Restart on crash; leave alone on clean exit -->
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<!-- Log both streams for post-reboot diagnosis -->
<key>StandardOutPath</key>
<string>/Users/agent/agent-workspace/agent.log</string>
<key>StandardErrorPath</key>
<string>/Users/agent/agent-workspace/agent-err.log</string>
<!-- Run once immediately when loaded (and after each login) -->
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
Apple's guide warns that a job which exits within 10 seconds of launch may be treated as crashing and throttled — by default launchd will not spawn it more than once every 10 seconds. Your agent script should take at least that long to exit on startup; a short health check (DNS, API reachability, Keychain unlock) is a good way to spend that time usefully.
Run this sequence in order after a reboot. Each phase has one acceptance check so you know it passed before continuing.
# Is the agent known to launchd?
launchctl list | grep com.yourteam.agent
Expected output: a row with the label and either 0 (not currently running) or a PID. If the label is missing, reload the plist:
launchctl load ~/Library/LaunchAgents/com.yourteam.agent.plist
Acceptance check 1: the label appears in launchctl list.
# Compare the last boot time to the newest log line
last reboot | head -1
tail -20 ~/agent-workspace/agent.log
The log should show at least one entry timestamped after the reboot. If it is empty or stale, read agent-err.log for a startup error.
Acceptance check 2: at least one log line timestamped after the reboot.
Keychain items survive a reboot, but they will prompt for access the first time they are retrieved after a reboot if the Keychain is not set to stay unlocked. Set the login Keychain to not auto-lock on timeout or sleep:
# Disable the timeout and sleep auto-lock on the login keychain
security set-keychain-settings ~/Library/Keychains/login.keychain-db
Then confirm the agent can retrieve its secret without a GUI prompt:
security find-generic-password -a "$USER" -s "llm-api-key" -w >/dev/null && echo OK
If this triggers an interactive Keychain unlock, the agent will block unattended after a reboot. Keep the secret in the system Keychain owned by the agent user, or source it from the CI secret manager at job start instead.
Acceptance check 3: security find-generic-password returns with exit code 0 and no GUI prompt.
Push one known-good task through your queue and confirm it is processed within two poll intervals:
# After enqueuing the canary, wait 3 minutes then inspect the agent's output
sleep 180
git -C ~/agent-workspace log --oneline -3
Acceptance check 4: the canary commit (or equivalent downstream artefact) appears.
If an acceptance check fails, use this list first:
launchctl list: plist is not in ~/Library/LaunchAgents/ or is malformed — validate with plutil -lint ~/Library/LaunchAgents/com.yourteam.agent.plist.ls -la /Users/agent/agent-workspace/agent-loop.sh.security set-keychain-settings without a timeout, and make sure the item's ACL lists the agent binary.agent-err.log normally tells you which.Running the acceptance checks by hand after every reboot is fine during setup, but for ongoing operation, watch the log from a second host or a monitoring service. Alert when no new line has been written in more than 2× your StartInterval. That catches both a reboot that launchd did not recover from and a running-but-stalled agent.
# On a monitoring host — alert if agent.log has not been modified in 10 minutes
find ~/agent-workspace/agent.log -mmin +10 -exec \
curl -s -X POST "$ALERT_WEBHOOK" -d '{"text":"Agent log stale"}' \;
The virtual Intel macOS plan is $29/month on Figment's Mac hosting page. It is a monthly charge, not pro-rated by uptime, so keeping an agent online for a partial month still costs the full $29. If your agent only runs Python, Node, bash or Shortcuts, that is the plan to start on.
Move up only when the workload actually needs it: the physical i3 Mac starts at $49/month for dedicated Intel hardware, and M-series plans start at $95/month when the agent has to execute arm64-only binaries or run Xcode 27 (exact chip, RAM and disk are confirmed in the quote). The Managed Build Mac at $249/month includes one M-series Mac, CI setup, backups, monitoring and 30 minutes of monthly support — worth it if you want someone else to re-run these acceptance checks for you after each macOS update.
Start a hosted Mac request · Mac hosting plans
Related: Mac agent API polling · Mac agent large uploads · GitHub Actions self-hosted runner on Mac