
A macOS update applied at the wrong moment terminates in-flight jobs, changes tool versions, and can break a dependency chain your automation has relied on for months. The fix is not to skip updates — it is to control when they happen and to have a tested rollback. This page gives you that runbook.
for a single long-running agent, run on the Figment virtual Intel macOS environment at $29/month and own the update cadence from the browser console. Teams that want a rotation (one Mac updating while the other keeps jobs moving) should price the $999 Managed Mac Team plan: two M-series Macs, two CI runners, team setup, backups, monitoring, and one hour of monthly support. Physical i3 ($49) and M-series (from $95) plans collect requests until supply is confirmed, so quote the exact chip/RAM/disk you need before committing.
Pick the plan before you plan the maintenance window — the update path differs for each.
| Figment plan | Monthly | Update path |
|---|---|---|
| Virtual Intel macOS (2 vCPU, 8 GiB RAM, 80 GiB disk) | $29 | You run softwareupdate from the VM's browser-console desktop terminal |
| Physical i3 Mac | $49 | Request-based; coordinate the window with Figment |
| Physical M-series Mac | from $95 | Request-based; confirm chip/RAM/disk and window in the quote |
| Managed Build Mac (one M-series, CI setup, backups, monitoring, 30 min support) | $249 | Discuss the window with Figment; monitoring will flag a stuck agent |
| Managed Mac Team (two M-series, two CI runners, team setup, backups, monitoring, 1 hr support) | $999 | Rotate: update one node while the other keeps running |
The $29 VM runs macOS on Linux/KVM with no GPU acceleration or audio. It is Intel, so it will run compatible Intel Xcode 26-or-earlier toolchains for cross-compiled arm64 builds and most CLI-driven agent work; choose an M-series plan if you need native ARM execution, Xcode 27, visionOS, or GPU-bound tasks (Apple: porting your macOS apps to Apple silicon).
A safe update needs three artifacts ready before you touch anything:
The simplest drain mechanism is a sentinel file the job wrapper checks at the top of each run:
#!/usr/bin/env bash
set -euo pipefail
DRAIN_FLAG="$HOME/agent/.drain"
if [[ -f "$DRAIN_FLAG" ]]; then
echo "Drain flag set — agent will not start a new job" >&2
exit 0
fi
# ... rest of job logic
Pair this with a launchd plist whose KeepAlive dictionary sets SuccessfulExit to false — launchd will relaunch only on a non-zero exit, so a clean exit under drain stops the restart loop. See the launchd.plist(5) man page and Apple's Creating Launchd Jobs for the exact plist schema.
Work through these phases in order; each one has an explicit pass check before you proceed.
touch ~/agent/.draintail -f /tmp/agentjob.log.launchctl list | grep agentjob — the PID column should be -.Record the environment so you know exactly what to restore:
#!/usr/bin/env bash
set -euo pipefail
OUT="$HOME/agent/checkpoint-$(date -u +%Y%m%dT%H%M%SZ)"
mkdir -p "$OUT"
sw_vers > "$OUT/os-version.txt"
xcode-select -p > "$OUT/xcode-path.txt" || true
brew list --versions > "$OUT/brew-versions.txt"
brew bundle dump --file="$OUT/Brewfile" --force
launchctl list > "$OUT/launchctl-list.txt"
ln -sfn "$OUT" "$HOME/agent/checkpoint-latest"
echo "Checkpoint saved to $OUT"
Copy the checkpoint directory to storage outside the VM (object storage, your own server, or a git repo you control) before updating. Do not place secret values in the checkpoint; capture only names and versions.
Apply the update from the browser-console terminal. The hosted Mac is running headlessly, so the terminal session drives the installer:
# List available updates
softwareupdate --list
# Install all recommended updates (patches only — no major macOS upgrade)
sudo softwareupdate --install --recommended --restart
# Or install one by exact label from --list
sudo softwareupdate --install "macOS Sequoia 15.x.x-..." --restart
--recommended applies security and reliability patches without triggering a major macOS version jump. A major upgrade on a hosted VM should be a deliberate decision, not a side effect — confirm your Intel-compatible Xcode/toolchain still supports the target macOS before you take it. If your organization pushes updates through MDM instead of the CLI, check Apple's current MDM deferral documentation before relying on timing you tested on an earlier macOS.
After the machine comes back up, run a verification script before removing the drain flag:
#!/usr/bin/env bash
set -euo pipefail
CP="$HOME/agent/checkpoint-latest"
echo "=== OS version ==="
sw_vers
echo "=== Brew bundle check ==="
brew bundle check --file="$CP/Brewfile" \
|| { echo "FAIL: missing Homebrew packages"; exit 1; }
echo "=== Agent smoke test ==="
# Use your real smoke test — do not count a failed build as a success.
shortcuts run "AgentSmokeTest" \
|| { echo "FAIL: smoke test"; exit 1; }
echo "All checks passed"
The shortcuts command is documented by Apple at Run shortcuts from the command line. Replace AgentSmokeTest with a shortcut or script that exercises the same code paths your production job does, including any network call your agent depends on. If the script exits non-zero, do not remove the drain flag — move to rollback.
Resume: rm ~/agent/.drain. The next launchd trigger starts the agent again.
Rollback if verification fails:
| Situation | Action |
|---|---|
| Brew packages broken | brew bundle install --file="$HOME/agent/checkpoint-latest/Brewfile" |
| Tool version regressed | Re-install via Brewfile or brew install formula@version |
| Core dependency broken by OS change | Re-provision a fresh Mac and restore agent config from the checkpoint |
| Cannot restore in place | Request a new VM or physical Mac from Figment and replay the checkpoint |
1. Drain touch ~/agent/.drain
wait for the running job to finish
2. Checkpoint ./save-checkpoint.sh
copy checkpoint dir off the host
3. Update sudo softwareupdate --install --recommended --restart
4. Verify ./verify-after-update.sh
PASS -> remove drain flag
FAIL -> rollback; keep drain flag set
5a. Resume rm ~/agent/.drain
5b. Rollback brew bundle install | re-provision + replay checkpoint
tail -50 /tmp/agentjob.err; a changed PATH or a missing CLI is the usual cause.brew list --versions <formula>; brew upgrade or pin with brew pin.open -a Shortcuts, then retry.launchctl load ~/Library/LaunchAgents/com.yourteam.agentjob.plist and check log show --last 5m --predicate 'subsystem == "com.apple.launchd"'.Billing on the $29 virtual Intel macOS plan is monthly; a maintenance window during the month does not change the invoice. Pick a natural quiet period in your schedule rather than optimizing the clock.
A small worksheet for teams considering the rotation pattern on Managed Mac Team ($999/month, two M-series Macs, two CI runners, one hour of monthly support included): if a single-node update usually takes about 90 minutes of agent downtime and you run four maintenance cycles per quarter, running on two nodes removes roughly 4 × 90 = 360 minutes of job backlog per quarter without you having to coordinate a stop-the-world window. Whether that saves money depends on your job value per hour and whether that hour of included support covers the kind of help you actually need; price it against your own backlog cost before upgrading from the $249 Managed Build Mac (one M-series Mac, CI setup, backups, monitoring, 30 minutes of support) to the Team plan.
Taxes and fees apply as stated in the offer you receive at checkout or in your quote.
Request a Figment Mac and run the Phase 2 checkpoint script before you touch an update — that way your first rollback target exists on day one, not after the first incident.
Start a hosted Mac at Figment → · Browse Mac hosting plans →
Related guides: