Decision: Pilot Runner Scale Set Client this week only if you can automatically provision, initialize, recycle, and externally log real Mac nodes. If you cannot, keep a fixed Mac Runner pool and add warm capacity with controlled manual expansion.
Timeline: Validate node delivery first, then JIT lifecycle, build reproducibility, queue behavior, security, and total operating cost. Do not treat a registered runner as proof that elastic scaling works.
This week’s action: Take one disposable remote Mac, run a non-release GitHub Actions workflow, verify automatic cleanup and log retention, and record every handoff from queue admission to node destruction.
This article is for DevOps engineers maintaining several GitHub Actions Mac Runner instances, mobile platform teams isolating signing work, and CI infrastructure owners comparing fixed nodes, warm pools, and per-job Mac capacity.
Last updated September 6, 2026. Status and lifecycle claims were checked against the official Runner Scale Set Client repository, GitHub Actions runner documentation, and the GitHub REST API documentation.
The control plane does not supply the Mac
Runner Scale Set Client can coordinate scale-set signals and JIT runner configuration for a custom scaling system. It does not purchase, boot, initialize, or destroy your Mac hosts. Those responsibilities remain in your infrastructure layer. GitHub’s official repository still labels the client as Public Preview, so interface and support assumptions require periodic review rather than production certainty.
Can Runner Scale Set Client support a macOS self-hosted Runner?
Yes, the official material describes a custom runner scaling approach that can include macOS. The important qualification is that macOS support at the client level does not mean your platform already knows how to obtain a Mac, select an image, install Xcode, register the runner, or remove the host after the job.
Your proof must come from delivery records, not from a successful API response. For each test node, retain evidence for:
- Capacity request accepted by your provisioning system.
- A real macOS host assigned to the request.
- Required architecture, Xcode, simulator runtime, and storage state verified.
- Runner configuration issued and registration completed.
- A real workflow completed or failed with a usable reason.
- Workspace, credentials, and runner registration removed.
- The host either destroyed or returned to an explicitly approved pool.
- Logs copied outside the node before termination.
If your platform can only attach an existing machine, you have fixed-pool scheduling, not elastic Mac scaling. That can still be a sound design. It is safer than claiming that a Scale Set Client integration creates capacity when it only dispatches work to capacity you already maintain.
Can you use Runner Scale Set Client without Kubernetes?
You do not need to turn this decision into an ARC or Kubernetes deployment project. The client repository describes a workflow that can be integrated with your own provisioning and orchestration layer. A non-Kubernetes implementation still needs the same contracts: a scale signal, node allocation, environment preparation, JIT configuration, job completion handling, cleanup, and failure recovery.
The absence of Kubernetes removes one orchestration option. It does not remove the operational work.
Warning: Keep organization names, repository names, App IDs, tokens, node names, and filesystem paths as placeholders in design documents and examples. A scaling proof should test the lifecycle, not expose production credentials.
Fixed, warm, and temporary Mac capacity make different trade-offs
The main decision is not “scale or do not scale.” It is how much state you allow to survive between jobs.
Long-lived Runner
A long-lived Mac Runner is continuously registered and normally reused. It has the lowest provisioning overhead because the operating system, Xcode installation, simulator data, and caches already exist.
Its weakness is accumulated state. A failed build can leave modified files, derived data, credentials, simulator state, package artifacts, or tool configuration for the next workflow. The risk becomes more serious when unrelated repositories or trust levels share the same workspace.
Use this model when:
- Workloads are stable and trusted.
- The node is isolated by repository, team, or workload class.
- You can reset the workspace and inspect drift.
- Build demand does not justify a more complex delivery system.
- Physical or persistent state is required and can be controlled.
Do not use a shared long-lived node for release signing merely because it is fast. Fast registration does not compensate for unclear state boundaries.
Single-job temporary Runner
A temporary node is created or selected for one job, receives a JIT configuration, executes the workflow, and is removed or rendered unusable afterward. GitHub recommends temporary self-hosted runners for stronger isolation patterns, and its security guidance explains why sensitive information should not be left available to later jobs. Review the self-hosted runner lifecycle guidance and the secure-use guidance for GitHub Actions before approving this design.
The benefit is a smaller reuse boundary. The cost is that every job must recover its environment, including dependencies, simulator runtimes, credentials, certificates, and caches. If reconstruction is incomplete, the runner may accept work successfully but fail during the actual Xcode build.
Use this model for:
- Signing and release workflows.
- Untrusted pull request separation.
- Public repository workloads.
- Jobs that must leave no workspace for a later task.
- Teams that already have reliable Mac image and cleanup automation.
Warm temporary capacity
A warm pool keeps a controlled number of prepared Macs available but does not treat them as permanent shared workers. A job claims one, applies a JIT configuration, runs, exports logs, and returns the host for reset or destroys it according to the trust level.
This approach reduces the first-job wait without forcing every node to remain permanently registered. It also makes capacity planning more explicit: you can measure how often warm nodes are occupied, how long they remain idle, and when manual expansion is needed.
A warm pool is often the sensible bridge when demand varies but full automatic provisioning is not ready. It gives you an operational fallback while the delivery path matures.
First metric: can the platform deliver a usable node?
The first milestone is not runner registration. It is a complete delivery transaction.
Define a node request with a stable identifier. Then connect that identifier across the capacity signal, host allocation, initialization logs, JIT configuration, workflow run, cleanup event, and external log record. If one stage cannot be joined to the next, troubleshooting will become guesswork when a queued build fails.
Your delivery test should follow these steps:
- Create a disposable capacity request from the scaling controller.
- Allocate a real remote Mac with the required architecture and approved access method.
- Apply the expected macOS settings, developer tools, Xcode version, simulator runtime, package managers, and repository access.
- Run an environment manifest and store its result outside the node.
- Generate or request the JIT configuration through the approved GitHub API path.
- Register the runner with only the labels required by the workflow.
- Execute a non-release build.
- Export workflow, runner, provisioning, and cleanup logs.
- Remove the runner configuration and destroy or reset the host.
- Repeat the request after an induced initialization failure and verify rollback.
The Runner Scale Set Client workflow documentation is useful for mapping these handoffs. It should not be read as a promise that your Mac provider, image system, or cleanup service is included.
What should GitHub Actions queue scaling trigger?
A queue signal should trigger a capacity decision, not an unconditional machine purchase. Your controller should distinguish between a job that can wait for a fixed pool, a job that needs a warm node, and a job that requires a clean temporary host.
Record the reason for every decision. “No capacity” may mean the pool is full, the Mac failed initialization, the requested label is unavailable, the signing pool is intentionally closed, or the controller rejected a malformed request. These cases require different remediation.
Second metric: does JIT improve isolation without breaking delivery?
JIT Runner configuration creates a short-lived registration path, but it does not automatically clean the operating system, erase the workspace, revoke every credential, or prove that the host is safe for the next task.
The GitHub REST API for JIT runner configuration describes the API operation and its role in obtaining a temporary configuration. Treat the returned configuration as secret material. Do not place it in workflow output, broad telemetry, shell history, or persistent image layers.
Should a Mac Runner be temporary or long-lived?
Choose a temporary node when the cost of workspace residue, credential exposure, or task crossover is higher than the cost of rebuilding the environment. Choose a long-lived node when the workload is trusted, stable, and protected by a tested reset process. Choose warm temporary capacity when you need shorter waits but cannot yet automate every provisioning step.
Keep separate pools for:
- Public repository workflows.
- Non-trusted pull requests.
- Ordinary development builds.
- Release and signing jobs.
- Administrative or diagnostic tasks.
A runner accepting a job proves only that the registration path worked. It does not prove that the job was authorized to use the node, that secrets were scoped correctly, or that the node will be clean afterward.
For signing, require a separate node class, separate labels, separate access policy, and a destruction or reset event that is independently logged. Never let a general build workflow select the signing pool through a broad label.
Third metric: measure the cold path instead of total build time
A total workflow duration hides the decision you need to make. Split the path into queue wait, Mac delivery, environment initialization, and runner registration. Then measure the actual build separately.
Do not invent a universal acceptable threshold for these stages. The correct boundary depends on your queue pattern, Xcode environment, node source, cache policy, and release deadlines. Use your own records from fixed, warm, and temporary strategies.
Run the comparison as a timeline:
- Queue admission: when the job becomes eligible for a Mac Runner.
- Capacity decision: when the controller chooses an existing, warm, or new node.
- Node delivery: when the Mac becomes reachable and passes identity checks.
- Environment preparation: when tools, dependencies, runtimes, and policies are ready.
- JIT registration: when the runner can accept the intended job.
- Execution: when the build starts and ends.
- Recovery: when logs are stored and the node is reset or destroyed.
Pure JIT creation can reduce idle capacity, but it adds a first-job wait. A minimum warm pool can reduce that wait while creating idle cost and reset work. Fixed nodes can be easier to operate when demand is steady, but they preserve more state and may require manual capacity planning.
A decision matrix for the first pilot
| Option | Delivery requirement | Isolation | Queue behavior | Best fit | Main failure mode |
|---|---|---|---|---|---|
| Fixed long-lived pool | Existing Macs and reset checks | Lowest unless strongly partitioned | Predictable while capacity remains | Stable trusted builds | Workspace drift and capacity exhaustion |
| Warm temporary pool | Prepared Macs plus claim and reset logic | Higher than shared fixed nodes | Lower first-job wait | Variable demand with incomplete automation | Idle capacity or failed reset |
| Per-job temporary node | Automatic Mac delivery, initialization, JIT setup, cleanup, and external logs | Strongest when cleanup is verified | Highest cold-start exposure | Signing, untrusted work, strict isolation | Provisioning failure or unreproducible environment |
Choose pure temporary scaling only after the last row has passed a failure test. If node delivery or cleanup remains partly manual, use the warm-pool row as the operating model and document the manual expansion point.
Fourth metric: can a new Mac reproduce the build?
A new node is useful only if it can execute the same workflow without hidden state from a previous machine. Runner Scale Set Client does not create your macOS image, install Xcode, restore a simulator runtime, populate dependency caches, open a graphical session, or initialize a signing environment.
How should a JIT Runner handle Xcode caches and code signing?
Treat caches as disposable accelerators, not correctness requirements. The first build on a new node should be allowed to run without a private cache. A later cache-assisted build can measure the benefit, but a cache miss must not change the expected result.
For each candidate image, verify:
- Xcode and command-line tool selection.
- Project dependency resolution.
- Simulator runtime availability.
- Apple platform SDK selection.
- Architecture and shell environment.
- Build settings and secret injection.
- Certificate and provisioning profile availability.
- Keychain unlock behavior where required.
- Derived data and package cache boundaries.
- Cleanup behavior after a failed build.
Use three evidence groups: a first build on a fresh node, a cache-assisted build on the same controlled path, and a rebuild after the node has been destroyed and recreated. Compare artifacts, failure reasons, environment manifests, and logs. Do not treat a faster second run as proof that the image is reproducible.
A release workflow should also prove that signing material is absent after cleanup. If you cannot independently verify removal or destruction, classify the node as unsuitable for sensitive signing.
Fifth metric: permissions and external logs decide production readiness
Use an organizational GitHub App or another narrowly scoped authentication design where it fits your control model. Avoid making a personal access token the hidden dependency for every provisioning action. Review the official authentication and self-hosted runner API reference for the current request and response behavior.
Your review should answer:
- Who can request a JIT configuration?
- Which organization, repository, or runner group can it affect?
- Who rotates the credential?
- What happens when the credential expires or is revoked?
- Can the provisioning system create a node without receiving a valid job?
- Can a failed job trigger cleanup?
- Can an operator revoke a stuck runner?
- Which logs survive node destruction?
- Can you connect a failed workflow to its node and cleanup attempt?
Store runner, provisioning, workflow, and destruction events outside the Mac. A local log is not enough if the node is deleted before an operator reads it. External storage should preserve the event sequence without copying raw secrets or unnecessary signing material.
Separate observability from credential capture. Mask sensitive fields, restrict log access, and use placeholders in examples and runbooks. A complete audit record says what happened and when; it does not need to reproduce a token.
Sixth metric: total cost includes operations, not just runtime
Compare the complete operating model rather than the visible time spent compiling. Include:
- Mac rental or ownership period.
- Warm-node idle time.
- Provisioning and environment initialization.
- Failed node rebuilds.
- External log storage.
- Image and Xcode maintenance.
- Credential rotation.
- Queue triage.
- Manual recovery.
- Engineering time spent maintaining the integration.
A per-job model may appear efficient when demand is low, yet become frustrating if every failure requires an operator to repair image state. A fixed pool may appear expensive during quiet periods, yet remain the better choice if its utilization is steady and its reset process is reliable.
Use queue records and node utilization from your own environment. Do not publish a generic capacity or cost threshold without a measured workload. The useful comparison is whether the elastic path gives you lower idle waste and equal or better recovery behavior than the fixed remote Mac pool.
Your three-way decision
Choose pilot when your platform can automatically deliver a real Mac, prepare the environment, issue JIT configuration, execute a representative workflow, preserve external logs, and destroy or reset the node after success and failure.
Choose dual track when demand fluctuates but one or more lifecycle stages still require intervention. Keep fixed or warm capacity for predictable work, and use temporary nodes for controlled non-release experiments. Record every manual step as a candidate automation task.
Choose defer when demand is stable, the Mac pool is small, or node delivery and cleanup remain mostly manual. A reliable fixed Runner is better than an unproven elastic system that fails during a release window.
Where a remote Mac fits the pilot
If you need disposable Mac capacity for validation, VMSPIN can provide a remote Mac environment without forcing you to purchase another physical host. Review the available Mac rental options only after defining the test evidence you need. For a regional trial, you can also inspect the remote Mac ordering path.
The current alternative has real limitations. A fixed local Mac or Mac mini can tie up capital, limit geographic access, and leave you responsible for hardware maintenance. A shared long-lived node can preserve workspace residue and make signing isolation harder. A generic Linux cloud host cannot replace macOS-only toolchains such as Xcode. These are reasons to test a remote Mac pool, not reasons to skip lifecycle validation.
Start with an independently rebuildable test node and a non-release workflow. Prove delivery, JIT registration, single-job cleanup, external log retention, and node recreation before expanding the pool. If the evidence supports the pilot decision, scale the rental capacity in stages; if it does not, keep the fixed or warm design and avoid paying for automation that has not earned production trust.