scripts/agent.mjs: macOS launchd install + start support - #29672
Conversation
|
Updated 4:55 PM PT - Apr 24th, 2026
❌ @dylan-conway, your commit 35eecdf has 4 failures in
🧪 To try this PR locally: bunx bun-pr 29672That installs a local version of the PR into your bun-29672 --bun |
The repo's agent.mjs only handled Windows (nssm), systemd, and openrc;
the macOS CI fleet was running a hand-patched fork with launchd support
that never made it back into the repo. This ports that path in so new
macOS test runners can be provisioned with one command.
- macOS paths: ~/Library/{Services,Caches,Logs,Preferences}/buildkite-agent
- `install` on macOS:
- writes ~/Library/Preferences/buildkite-agent.cfg with token + queue
(no `spawn=` — tests assume sole machine ownership; scale with more
boxes, not more workers per box)
- writes /Library/LaunchDaemons/buildkite-agent.plist (KeepAlive,
WatchPaths on the cfg) and com.buildkite.cleanup.plist (daily wipe +
reboot at 06:30), then `launchctl bootstrap`s both
- re-running install preserves an existing token line if
BUILDKITE_AGENT_TOKEN isn't supplied
- `start`/`exec` (the launchd entrypoint):
- passes --config <cfg> instead of --token on macOS
- emits the tags ci.mjs actually targets: release=<macOS major>,
posix=true/windows=false, ephemeral=false
- keeps `%spawn` only in the agent name template so names match the
existing fleet's `-1` suffix
- new --queue flag (default test-darwin)
- tag filter now keeps explicit `false` values (previously dropped)
Usage on a fresh macOS box (after bootstrap.sh + tailscale up --ssh):
sudo BUILDKITE_AGENT_TOKEN=<token> node scripts/agent.mjs install --queue=test-darwin
Compared the new code against the live aarch64 test box and fixed four
discrepancies:
- Agent name: `darwin-<arch>-<distro-version>-%spawn` instead of
`<hostname>-%spawn`. The macOS boxes have meaningless asset-ID
hostnames (e.g. 66783.local); the deployed naming tells you what the
agent is at a glance and matches the existing fleet.
- install now copies agent.mjs + utils.mjs into
~/Library/Services/buildkite-agent/ and points the plist there, so the
service doesn't depend on the checkout that ran `install` surviving.
- plist node path: resolve via which("node") (the stable
/opt/homebrew/bin/node symlink) rather than process.execPath, which
can be a brew Cellar path that breaks on upgrade.
- cleanup plist: adopt the deployed script verbatim — covers both the
Homebrew-agent layout (older x64 boxes) and the Library layout, fixes
ownership, then reboots at 06:27.
Also added /opt/rust/bin and ~/go/bin to the plist PATH to match.
The previous `go install tailscale.com/cmd/tailscale{,d}@latest` puts
binaries in ~/go/bin and stops — no system daemon, no version info
(shows ERR-BuildInfo), no upgrade path. The fleet ended up with
hand-copied binaries in /usr/local/bin and hand-written plists that
diverged across boxes.
Switch to `brew install tailscale` + `tailscaled install-system-daemon`:
- proper version reporting
- standard /Library/LaunchDaemons/com.tailscale.tailscaled.plist
- `brew upgrade tailscale` works
- state at /Library/Tailscale/ carries over, so re-running on an
already-authed box keeps connectivity
Also clears any leftover go-installed real-file binaries at
/usr/local/bin/tailscale{,d} so brew link succeeds, and on arm64
(brew prefix /opt/homebrew) symlinks into /usr/local/bin so root's
PATH and any existing plist still find the binary.
Bumps Version: 29 -> 30.
The rm/relink/symlink steps were there to migrate boxes that had the old go-install layout. bootstrap.sh should assume a clean machine; existing boxes get migrated out-of-band.
Under sudo, process.env.USER is 'root', so the launchd plist got UserName=root and the cfg/dirs were root-owned mode 0600. The service then ran as root, homedir() resolved to /var/root, and start() couldn't find the cfg -> 'Buildkite token not found' crash loop. Use SUDO_USER for the plist UserName and chown the written files to that user before bootstrapping. Verified on the first new macOS 26 runner (darwin-test-arm64-3).
…/15) Each PR now runs darwin-aarch64 once on a macOS 26 agent and once on whatever older version is free (13/14/15). x64 runs once in the "previous" pool only — Intel Macs cap at macOS 15 and MacStadium no longer stocks them, so there is no x64 "latest" pool. agent.mjs: emit `release-tier` tag (`latest` if macOS major >= LATEST_DARWIN_RELEASE=26, else `previous`). Bump the constant when a new macOS ships and the first 26+1 runner is online; older boxes fall into `previous` automatically. ci.mjs: getTestAgent() targets by `release-tier` for darwin so the two arm64 entries actually land on distinct OS pools instead of both going to whatever's free. Rollout: agents must emit `release-tier` BEFORE this ci.mjs change takes effect (a PR's pipeline is generated from its own commit), so the agent.mjs update needs to be deployed to the static fleet first.
05a37ed to
80c8039
Compare
arm64: latest(26) + previous(14/15). x64: previous(14/15) + oldest(13). Intel can't run latest; 13 is the min-supported floor we want explicit coverage on. agent.mjs computes the tier from the macOS major version via LATEST_/PREVIOUS_DARWIN_RELEASE thresholds.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (3)
Disabled knowledge base sources:
WalkthroughRefactor Darwin/macOS CI agent configuration and setup. Switch test platform selection from release values to tier attributes, expand agent.mjs with queue option and macOS-specific installation/configuration logic, and simplify Tailscale installation to use Homebrew instead of building from source. Changes
Comment |
There was a problem hiding this comment.
I didn't find bugs, but this is CI-infrastructure work (launchd provisioning + changing how darwin test jobs are routed via the new release-tier tag) with unchecked deploy items in the test plan, so it should get a human look before merge — in particular to confirm the existing fleet already emits release-tier so darwin test jobs don't go unschedulable once ci.mjs starts targeting it.
Extended reasoning...
Overview
This PR touches three CI/infra files: .buildkite/ci.mjs (reshapes the darwin test matrix from 2 → 4 entries and adds a release-tier selector to getTestAgent), scripts/agent.mjs (~130 new lines implementing macOS launchd install — writes buildkite-agent.cfg, two LaunchDaemon plists including a daily-reboot cleanup job, copies the script into ~/Library/Services, chowns, and launchctl bootstraps; plus new agent tags release, release-tier, posix, windows, ephemeral), and scripts/bootstrap.sh (switches macOS tailscale from go install to Homebrew + tailscaled install-system-daemon).
Security risks
No product-code security exposure — this is internal CI provisioning. The agent token is written to a mode-0600 cfg and the daily cleanup script runs rm -rf / shutdown -r as root, but inputs are operator-controlled (sudo invocation on a CI box), not attacker-reachable.
Level of scrutiny
Medium-high operationally. The ci.mjs change is load-bearing for every PR: adding release-tier to the darwin agent selector means jobs will only schedule on agents that already advertise that tag. The PR description says the live fleet runs a hand-patched fork of this script — if that fork doesn't already emit release-tier, merging this could leave darwin test jobs stuck waiting for agents until the fleet is re-provisioned. That deployment ordering needs a human who knows the fleet state to confirm. The matrix change also reverses the prior documented decision to halve darwin job count; presumably intentional now that tier-based routing gives real coverage, but worth a maintainer ack.
Other factors
The PR's own test plan has the two real-world checks (fresh MacStadium deploy, idempotent re-install) still unchecked, and Buildkite is currently red on this commit. The agent.mjs logic itself reads coherently (token preservation, SUDO_USER handling, cfg-vs-token in start), and the bug-hunting pass found nothing, but given the operational coupling and incomplete validation this isn't something I'd auto-approve.
## Summary
Ports macOS launchd support into `scripts/agent.mjs` so new macOS test
runners can be provisioned with one command. The current macOS CI fleet
runs a hand-patched fork of this script that never landed in the repo;
this brings the repo version up to parity.
- macOS paths under
`~/Library/{Services,Caches,Logs,Preferences}/buildkite-agent`
- `install` on macOS writes `buildkite-agent.cfg` (token + queue, **no
`spawn=`** — the test suite assumes it owns the machine), the launchd
plist, the daily-reboot cleanup plist, and `launchctl bootstrap`s both.
Re-running preserves an existing token.
- `start` / `exec` passes `--config <cfg>` and emits the tags `ci.mjs`
targets (`release=<macOS major>`, `posix`/`windows`, `ephemeral=false`).
- New `--queue` flag, default `test-darwin`.
## Usage
```sh
sudo BUILDKITE_AGENT_TOKEN=<token> node scripts/agent.mjs install --queue=test-darwin
```
## Test plan
- [x] `node --check scripts/agent.mjs`
- [x] Dry-run of `start` produces a command matching the live fleet's
flags/tags
- [ ] Deploy to one fresh MacStadium box, verify agent registers with
correct tags
- [ ] Re-run `install` on an existing box, verify cfg/plist are
idempotent
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
## Summary
Ports macOS launchd support into `scripts/agent.mjs` so new macOS test
runners can be provisioned with one command. The current macOS CI fleet
runs a hand-patched fork of this script that never landed in the repo;
this brings the repo version up to parity.
- macOS paths under
`~/Library/{Services,Caches,Logs,Preferences}/buildkite-agent`
- `install` on macOS writes `buildkite-agent.cfg` (token + queue, **no
`spawn=`** — the test suite assumes it owns the machine), the launchd
plist, the daily-reboot cleanup plist, and `launchctl bootstrap`s both.
Re-running preserves an existing token.
- `start` / `exec` passes `--config <cfg>` and emits the tags `ci.mjs`
targets (`release=<macOS major>`, `posix`/`windows`, `ephemeral=false`).
- New `--queue` flag, default `test-darwin`.
## Usage
```sh
sudo BUILDKITE_AGENT_TOKEN=<token> node scripts/agent.mjs install --queue=test-darwin
```
## Test plan
- [x] `node --check scripts/agent.mjs`
- [x] Dry-run of `start` produces a command matching the live fleet's
flags/tags
- [ ] Deploy to one fresh MacStadium box, verify agent registers with
correct tags
- [ ] Re-run `install` on an existing box, verify cfg/plist are
idempotent
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
|
Heads up: the |
|
The |
Summary
Ports macOS launchd support into
scripts/agent.mjsso new macOS test runners can be provisioned with one command. The current macOS CI fleet runs a hand-patched fork of this script that never landed in the repo; this brings the repo version up to parity.~/Library/{Services,Caches,Logs,Preferences}/buildkite-agentinstallon macOS writesbuildkite-agent.cfg(token + queue, nospawn=— the test suite assumes it owns the machine), the launchd plist, the daily-reboot cleanup plist, andlaunchctl bootstraps both. Re-running preserves an existing token.start/execpasses--config <cfg>and emits the tagsci.mjstargets (release=<macOS major>,posix/windows,ephemeral=false).--queueflag, defaulttest-darwin.Usage
Test plan
node --check scripts/agent.mjsstartproduces a command matching the live fleet's flags/tagsinstallon an existing box, verify cfg/plist are idempotent