Skip to content

scripts/agent.mjs: macOS launchd install + start support - #29672

Merged
dylan-conway merged 8 commits into
mainfrom
claude/agent-mjs-macos-setup
Apr 24, 2026
Merged

dylan-conway merged 8 commits into
mainfrom
claude/agent-mjs-macos-setup

Conversation

@dylan-conway

Copy link
Copy Markdown
Member

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 bootstraps 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

sudo BUILDKITE_AGENT_TOKEN=<token> node scripts/agent.mjs install --queue=test-darwin

Test plan

  • node --check scripts/agent.mjs
  • 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

@robobun

robobun commented Apr 24, 2026 •

Copy link
Copy Markdown
Collaborator
Updated 4:55 PM PT - Apr 24th, 2026

❌ @dylan-conway, your commit 35eecdf has 4 failures in Build #47840 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 29672

That installs a local version of the PR into your bun-29672 executable, so you can run:

bun-29672 --bun

dylan-conway and others added 7 commits April 24, 2026 15:52
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.
@dylan-conway
dylan-conway force-pushed the claude/agent-mjs-macos-setup branch from 05a37ed to 80c8039 Compare April 24, 2026 22:54
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.
@dylan-conway
dylan-conway marked this pull request as ready for review April 24, 2026 23:30
@dylan-conway
dylan-conway merged commit 64c585b into main Apr 24, 2026
63 of 65 checks passed
@dylan-conway
dylan-conway deleted the claude/agent-mjs-macos-setup branch April 24, 2026 23:30
@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3ba00628-bd8f-47fc-aec3-e0fb33a40bdd

📥 Commits

Reviewing files that changed from the base of the PR and between 3429283 and 35eecdf.

📒 Files selected for processing (3)
  • .buildkite/ci.mjs
  • scripts/agent.mjs
  • scripts/bootstrap.sh

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.


Walkthrough

Refactor 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

Cohort / File(s) Summary
CI Agent Configuration
.buildkite/ci.mjs
Updated test platform selection logic to use tier attribute instead of release values for Darwin agent selection. Modified testPlatforms matrix with distinct Darwin aarch64/x64 entries for different tiers, and updated getTestAgent to include release-tier in agent configuration for macOS.
macOS Agent Setup
scripts/agent.mjs
Added --queue CLI option support. Implemented macOS-specific install behavior including persistent agent config under ~/Library/Preferences, launchd plist generation, and file copying to make plist independent of checkout. Updated token handling for macOS to reuse existing config when token unavailable. Modified agent naming convention and enriched tags with release, release-tier, and platform-specific tags.
Tailscale Installation
scripts/bootstrap.sh
Switched macOS Tailscale installation from building from Go source to using Homebrew formula. Removed Go toolchain dependency and PATH modifications, leveraging tailscaled install-system-daemon from Homebrew.

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

structwafel pushed a commit to structwafel/bun that referenced this pull request Apr 25, 2026
## 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>
xhjkl pushed a commit to xhjkl/bun that referenced this pull request May 14, 2026
## 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>
@robobun

robobun commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Heads up: the com.buildkite.cleanup daemon this installs doesn't check for an in-flight Buildkite job before rm -rf builds/* && reboot, so a test shard that happens to be running at the scheduled minute gets its checkout deleted out from under it and reports spurious test failures (seen in build 73894 on darwin-naan-x64). #34367 adds a pgrep -qf 'buildkite-agent bootstrap' guard.

@robobun

robobun commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

The com.buildkite.cleanup daemon this installs wipes builds/ and reboots at 06:27 local time under running jobs, which fails a darwin shard most days (build 113713 is the latest). #40349 fixes the daemon, #42174 keeps the runner from turning it into a non-retried test failure.

@robobun

robobun commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator

The com.buildkite.cleanup daemon that this PR added to scripts/agent.mjs wipes builds/* and reboots at 06:27 local time under a running job, which fails a darwin shard most nights (build 118828 is the latest). #43611 makes it wait for the job.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants