What macOS runner minutes cost on GitHub Actions, and how to cut them

A minute on a GitHub-hosted macOS runner costs about ten times a minute on Linux, and iOS jobs are long. Here are the current prices from GitHub’s documentation, how they are counted, a worked monthly estimate, and the changes that bring the number down, roughly in the order they pay off.

TL;DR

  • Standard macOS runners (macos-26, macos-latest, macos-26-intel) cost $0.062 a minute; Linux 2-core costs $0.006.
  • Each job is rounded up to a whole minute. Included usage is a dollar amount: $12 a month on Free covers about 193 macOS minutes.
  • A team running 13,000 macOS minutes a month pays about $806, before the included amount.
  • Cut it in this order: stop runs nobody needs (cancel superseded, path filters, timeouts), move non-Xcode work to Linux, then make the remaining runs shorter (caching, test selection), and only then try bigger runners.

The prices

Per-minute rates for GitHub-hosted runners, from GitHub’s Actions runner pricing reference:

GitHub-hosted runner prices per minute
RunnerLabelsPer minute
macOS 3-core (M1)macos-26, macos-latest, macos-15, xcode-27$0.062
macOS 4-core (Intel)macos-26-intel, macos-15-intel$0.062
macOS 12-core (Intel), largermacos-26-large, macos-latest-large$0.077
macOS 5-core (M2 Pro), largermacos-26-xlarge, macos-latest-xlarge, xcode-27-xlarge$0.102
Linux 2-core (x64), for comparisonubuntu-latest$0.006

Larger runners are available on the Team and Enterprise Cloud plans only, and are billed even for public repositories.

How billing counts

  • Per job, rounded up. “GitHub rounds the minutes and partial minutes each job uses up to the nearest whole minute.” Three macOS jobs of 4 min 10 s are billed as 15 minutes, not 12.5.
  • Included usage is money, not minutes. Each plan includes compute per month: $12 on Free, $18 on Pro and Team, $300 on Enterprise Cloud. Runner usage is deducted at the rates above, so $12 is about 193 macOS minutes and $300 about 4,838.
  • Public repositories use standard GitHub-hosted runners for free; larger runners are billed.
  • Self-hosted runners are free on GitHub’s side and do not use the included amount. You pay for the Mac, its upkeep and its security instead.
  • Failed and re-run jobs count. GitHub’s own example: a 10-minute workflow that fails after 5 and is re-run costs 15 minutes.

A worked month

An example team, numbers chosen to be easy to follow. Put in your own:

  • 30 pull requests a week, 4 pushes each: 120 runs.
  • 30 merges to the default branch a week, which run the same job: 30 runs.
  • One job, build and test, 20 minutes on macos-26.
Example monthly cost
Minutes per week150 × 20 = 3,000
Minutes per month (× 52 / 12)13,000
At $0.062$806
The same minutes on Linux 2-core, for scale$78

Every minute taken off that job is worth 650 minutes, about $40, a month. That is the yardstick for the levers below.

Seven levers

1. Cancel runs that a newer push replaced

On a pull request, only the run for the latest commit matters. A concurrency group per branch cancels the older run when a new one starts. Keep default-branch runs, which other things may depend on:

.github/workflows/build.yml
concurrency:  group: ${{ github.workflow }}-${{ github.ref }}  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

In the example, if one push in four arrives while the previous run is halfway, this saves about 1,300 minutes a month, $81.

2. Don’t start macOS for changes that can’t affect it

.github/workflows/build.yml
on:  pull_request:    paths-ignore:      - '**.md'      - 'docs/**'      - '.github/ISSUE_TEMPLATE/**'

One catch: a workflow skipped by a path filter leaves its checks “Pending”, and a pull request that requires them cannot merge. If the job is a required check, keep the workflow running and skip the expensive steps inside it instead, or make a cheap Linux job the required check.

3. Cap hung jobs

A job’s default timeout is 360 minutes. A Simulator that never boots or a test that waits forever costs 360 × $0.062 = $22.32 per job. Set a ceiling a little above your slowest normal run:

.github/workflows/build.yml
jobs:  build:    runs-on: macos-26    timeout-minutes: 30

4. Move everything that isn’t Xcode to Linux

Linting, formatting checks, danger-style pull request checks, documentation builds and anything that only needs a shell run at a tenth of the price on ubuntu-latest. Split them into their own job so the macOS job only builds and tests.

5. Stop recompiling what didn’t change

Most of an iOS job is usually compilation. Caching DerivedData looks like the fix and mostly isn’t: why caching DerivedData doesn’t speed up Xcode builds. Cache Swift package checkouts by Package.resolved, and for compiled code use Xcode 26’s compilation cache with a cache that outlives the runner (how, and what it takes).

Where Warmbuild fits

Warmbuild is a hosted remote compilation cache for this lever: one step before xcodebuild; the default branch fills it and pull requests read it. In one measured case, a pull request’s build step went from 521 s to 212 s at 97% hits (how it was measured). In the example above, 5 minutes off the job would be about $200 a month. After each job it also reports where the minutes went, so you can see which lever applies to you. Set up the Action.

6. Run only the tests the change reaches

Tests are the other large share. Two things help regardless of tooling: boot the Simulator explicitly before the test step, so the boot doesn’t hide inside the first test’s timeout, and run only what a pull request can affect, with xcodebuild -only-testing: or -skip-testing:. Keep running everything on the default branch, so anything selection skipped is caught one push later.

.github/workflows/build.yml
- run: |    UDID=$(xcrun simctl list devices available -j | jq -r '[.devices[][] | select(.name == "iPhone 17")][0].udid')    xcrun simctl bootstatus "$UDID" -b    xcodebuild test -scheme App -destination "id=$UDID" -only-testing:AppTests

The device name is an example; pick one the runner image lists. Warmbuild’s select-tests: true computes the -only-testing arguments per pull request from what the diff reaches; in a replay of 100 default-branch commits of one app, the median commit ran 34 of 505 unit tests (test selection).

7. Pick the runner by cost per job, not per minute

The M2 Pro runner costs 1.65 times the standard rate. A 20-minute job costs $1.24 on macos-26; on macos-26-xlarge it costs less only if it finishes in under about 12 minutes. Measure the same job on both before switching. Self-hosted Macs remove GitHub’s per-minute charge, but a Mac that runs pull requests from forks runs untrusted code, and someone has to keep Xcode and the Simulators current.

Where to start

  1. Find where the minutes go: GitHub’s billing usage report per repository, and each job’s step timings.
  2. Remove runs: levers 1 to 3 are a few lines of YAML each and have no downside for most teams.
  3. Move work off macOS (lever 4).
  4. Shorten what is left: compilation (lever 5), then tests (lever 6).
  5. Re-measure, then decide on runner size (lever 7).

Sources

  1. GitHub Docs, Actions runner pricing: per-minute rates, rounding, larger runner rules. Checked 15 November 2026.
  2. GitHub Docs, GitHub Actions billing: included compute by plan, public repositories, self-hosted runners, the re-run example. Checked 15 November 2026.
  3. GitHub Docs, GitHub-hosted runners and Larger runners: labels and hardware. Checked 15 November 2026.
  4. GitHub Docs, Control the concurrency of workflows and jobs.
  5. GitHub Docs, Workflow syntax: paths-ignore, skipped workflows and required checks, timeout-minutes (default 360).
  6. Warmbuild docs, case note: billet and test selection.