
Running two automation projects as the same macOS user is how API keys from Project A end up in Project B's environment and a cleanup script wipes the wrong working directory. The fix is explicit separation: each project gets its own OS user, its own home directory tree, its own LaunchAgent namespace, and its own login Keychain. This page gives you the exact commands, a provisioning checklist, three failure checks, and the Figment plan that fits.
For two or three lightweight agents that run sequentially and build Intel-compatible targets, start on the $29/month Figment virtual Intel macOS environment (2 vCPU, 8 GiB RAM, 80 GiB disk). It runs real macOS and supports multiple user accounts; you reach it through the browser console terminal. Supported Intel Xcode (Xcode 26 or earlier) can cross-compile arm64 device apps and universal macOS binaries from this VM — see Apple's Porting Your macOS Apps to Apple Silicon for the current Intel toolchain boundary.
Move up to an M-series Mac from $95/month when you need any of: ARM-only execution, GPU work, Xcode 27, visionOS, or several concurrent agents each holding memory. Specific chip, RAM, and disk at that entry price are confirmed in the quote — ask before you commit. A team running multiple agents with CI runners and backups is better served by the $249/month Managed Build Mac (one M-series Mac, CI setup, backups, monitoring, 30 minutes monthly support) or the $999/month Managed Mac Team (two M-series Macs, two CI runners, team setup, backups, monitoring, one hour monthly support).
Document the following per project before you provision anything:
| Per-project input | Example |
|---|---|
| Short user account name | agent-projecta |
| Home directory | /Users/agent-projecta |
| Working directory | /Users/agent-projecta/workdir |
| Secret names (not values) | PROJECTA_API_KEY, PROJECTA_DB_URL |
| Automation binary path | /Applications/ProjectA/agent |
| Required network destinations | api.projecta.com:443 |
Keep secret names in your runbook. Load actual values into each user's own login Keychain, or inject them from your CI service's secret manager at job start. Never write secret values into provisioning scripts, LaunchAgent plists, or a shared .env file — the entire point of the separation is to avoid shared state that can leak between projects.
Separation has two layers: distinct OS users (the strong boundary) and mode-700 directory permissions (a lighter boundary when two jobs must share one user).
Run sysadminctl -h on your Mac to confirm the flag list for your macOS version, then create each user from an admin session in the browser console terminal:
# Project A agent user
sudo sysadminctl -addUser agent-projecta \
-fullName "Project A Agent" \
-password - \
-home /Users/agent-projecta \
-shell /bin/bash
sudo createhomedir -c -u agent-projecta
sudo chmod 700 /Users/agent-projecta
# Project B agent user
sudo sysadminctl -addUser agent-projectb \
-fullName "Project B Agent" \
-password - \
-home /Users/agent-projectb \
-shell /bin/bash
sudo createhomedir -c -u agent-projectb
sudo chmod 700 /Users/agent-projectb
The -password - form prompts interactively so no password is left in shell history. With chmod 700, only the owning user can list or read their home directory. Each user then has its own login Keychain under ~/Library/Keychains/, its own ~/Library/LaunchAgents/, its own environment scope, and its own crontab/at namespaces.
If one CI user must process repositories from different projects, keep each repository checkout in its own mode-700 directory and never cross-reference paths:
mkdir -p ~/workspaces/projecta ~/workspaces/projectb
chmod 700 ~/workspaces/projecta ~/workspaces/projectb
ls -la ~/workspaces/
# drwx------ ... projecta
# drwx------ ... projectb
This blocks accidental reads but does not stop a compromised job from reading sibling directories under the same uid. Prefer Layer 1 whenever you can.
Apple's Creating Launchd Jobs documents that agents in a user's ~/Library/LaunchAgents/ are launched by that user's own launchd and are not visible to other users. Install and load the plist in the target user's context, and set its mode to 600 as Apple requires for agents and daemons:
sudo -u agent-projecta install -m 600 \
/tmp/com.yourorg.projecta.plist \
/Users/agent-projecta/Library/LaunchAgents/com.yourorg.projecta.plist
AGENT_UID=$(id -u agent-projecta)
sudo launchctl bootstrap gui/"$AGENT_UID" \
/Users/agent-projecta/Library/LaunchAgents/com.yourorg.projecta.plist
bootstrap gui/<uid> is the current form on modern macOS; launchctl load is still accepted but deprecated. Run launchctl help on the target Mac to see the exact subcommands available in your release.
If this Mac is also a GitHub Actions self-hosted runner, review GitHub's hardening for self-hosted runners and run the runner service under the project's dedicated user, not under an admin account.
Walk this list once per new project workspace:
sysadminctl -addUser/Users/agent-projectX mode is 700 (ls -ld)700~/Library/LaunchAgents/ for that user only600bootstrap gui/<uid>)launchctl list as Project A's user does not show Project B's agentsls /Users/agent-projectb as Project A's user returns Permission denied$HOME paths only, never hardcoded /Users/agent-*Check 1 — cross-user read is blocked:
sudo -u agent-projecta ls /Users/agent-projectb/workdir
# Expected: ls: /Users/agent-projectb/workdir: Permission denied
Check 2 — LaunchAgent namespace is separate:
sudo -u agent-projecta launchctl list | grep projectb || echo "isolated"
# Expected: isolated
Check 3 — Keychain is scoped: add a test item to Project A's login Keychain with security add-generic-password, then run sudo -u agent-projectb security find-generic-password -a agent-projecta. It should fail to locate the item because the lookup runs against Project B's own Keychain.
If any of the three checks fail, stop and fix before loading real agents. A failed check is not a passing build.
| Scenario | Figment plan | Monthly | Annual at 12× |
|---|---|---|---|
| 2–3 sequential agents, Intel-compatible targets | Virtual Intel macOS | $29 | $348 |
| Concurrent agents or ARM/GPU work | M-series Mac (entry) | from $95 | from $1,140 |
| One managed CI Mac with backups + monitoring | Managed Build Mac | $249 | $2,988 |
| Team: two Macs, two CI runners, one hour support | Managed Mac Team | $999 | $11,988 |
Prepayment terms, taxes, and any setup fees appear on the actual quote at purchase. Capacity for physical Intel and M-series hardware is confirmed in the quote; the $49/month i3 Mac and physical M-series offers currently collect requests until supply is confirmed.
Request your configuration at Figment Mac Hosting, then move to the quick-start. Once the Mac is live:
sysadminctl -addUser block above for every project.To migrate from an existing host, export each project's LaunchAgent plist, rewrite any absolute paths under the new user's home, load secrets into the new login Keychain, bootstrap the plist in the target user's GUI domain, then re-run the failure checks.
Related guides: