# GitHub Actions macOS 14 Retirement: Fix Your Builds Before Nov 2

URL: https://www.devopsness.com/blog/github-actions-macos-14-retirement
Author: Kiril Urbonas
Published: 2026-10-26
Updated: 2026-10-26
Categories: DevOps
Tags: CI/CD, GitHub Actions, DevOps, Macos, Ios

Summary: GitHub retires the macOS 14 runner image on November 2, and brownouts are already failing jobs. Here is how to find every macos-14 reference and move without breaking Xcode.

---
# GitHub Actions macOS 14 Retirement: Fix Your Builds Before Nov 2

GitHub retires the macOS 14 runner image on November 2, 2026. Jobs on `macos-14`, `macos-14-large`, or `macos-14-xlarge` will stop running. Brownouts already fail them for ten hours at a time, including **today, October 26, from 14:00 to 24:00 UTC**, then again on October 29 and 30. Switch to `macos-15` or `macos-26`, and check your Xcode version first.

If your iOS or macOS pipeline went red this afternoon with no code change, this is probably why. The rest of this post covers how to find every reference, which label to move to, and the Xcode trap that turns a one-line change into a broken build.

## The dates that matter

All times are UTC, from GitHub's retirement notice.

| Date | What happens |
|---|---|
| Oct 26, 14:00 to Oct 27, 00:00 | Brownout: macOS 14 jobs fail |
| Oct 29, 14:00 to Oct 30, 00:00 | Brownout |
| Oct 30, 14:00 to Oct 31, 00:00 | Brownout |
| Nov 2, 2026 | Image retired; macOS 14 labels stop working |

Earlier brownouts ran on October 5, 12, 16, 19, and 23, so some teams have already seen unexplained failures and retried their way past them. GitHub also warns it may reduce macOS 14 capacity before retirement, so expect longer queue times on those labels even outside brownouts.

## Find every macos-14 reference

The label hides in more places than `runs-on:`. Matrix entries, reusable workflow inputs, and composite actions all count.

```bash
# In one repo: every workflow line that mentions macos-14
$ grep -rnE "macos-14" .github/

# Across your org (needs gh auth with read access)
$ gh search code "macos-14" --owner your-org --extension yml --limit 100 \
    --json repository,path --jq '.[] | "\(.repository.nameWithOwner) \(.path)"'

# Which recent runs actually used it, in one repo
$ gh run list --limit 50 --json name,conclusion,createdAt,url
```

Two places teams miss. First, `matrix.os` lists such as `os: [ubuntu-latest, macos-14]`, where the label never appears next to `runs-on`. Second, reusable workflows that take the runner as an input, so the caller passes `macos-14` from a different repository. Search the callers too. Our guide to [reusable workflows at org scale](/blog/github-actions-reusable-workflows-org-scale) covers how those inputs flow.

## Pick the replacement label

GitHub recommends four arm64 labels:

- `macos-15` and `macos-15-xlarge`: the closest step up, and the safer default for most pipelines.
- `macos-latest` and `macos-latest-xlarge`: these now point to `macos-26`. They will move again in the future without a change on your side.

We pin an explicit version (`macos-15` or `macos-26`) rather than `macos-latest`. A floating label is exactly how teams end up surprised by an image change like this one. Upgrade on purpose, on your schedule, with a PR you can roll back.

## The Xcode trap

This is where the simple label swap breaks builds. Xcode versions are tied to the runner image, per GitHub's `runner-images` READMEs:

| Image | Installed Xcode | Default |
|---|---|---|
| macOS 14 | 15.0.1 to 15.4, 16.1, 16.2 | 15.4 |
| macOS 15 | 16.0 to 16.4, plus 26.0.1 to 26.3 | 16.4 |
| macOS 26 | 26.0.1 to 26.6 | 26.6 |

**Xcode 15 does not exist on macOS 15.** If your workflow selects Xcode 15.x explicitly, or silently relies on the macOS 14 default of 15.4, moving to `macos-15` changes your compiler. Swift language mode, warnings-as-errors, and deprecated APIs can all surface at once.

Make the version explicit so the change is visible in review:

```yaml
# .github/workflows/ios.yml
jobs:
  build:
    runs-on: macos-15
    steps:
      - uses: actions/checkout@v7
      - name: Select Xcode
        run: sudo xcode-select -s /Applications/Xcode_16.4.app
      - run: xcodebuild -version
      - run: xcodebuild -scheme App -destination 'platform=iOS Simulator,name=iPhone 16' build
```

If you need the smallest possible jump, `Xcode_16.2.app` exists on both images, so you can pin 16.2 on `macos-14` today, get green, then flip the label. That splits one risky change into two boring ones.

Also check simulator destinations. A `-destination` naming a device or OS runtime that isn't installed on the new image fails at the build step, not the label. The image README lists the installed runtimes.

## Cost and capacity notes

Larger runners keep their billing class: `macos-14-xlarge` maps to `macos-15-xlarge` or `macos-latest-xlarge`, both billed as larger runners. macOS minutes are already the most expensive minutes on GitHub Actions, so this is a good moment to check whether every macOS job needs to run on every push; see our breakdown of [GitHub Actions pricing](/blog/github-actions-pricing). Moving lint, unit tests that don't need a simulator, and packaging to Linux runners can cut the macOS bill substantially.

## Questions people ask

### When is the GitHub Actions macOS 14 runner retired?

On November 2, 2026. After that, jobs using `macos-14`, `macos-14-large`, or `macos-14-xlarge` won't run. Before retirement, GitHub runs brownouts that temporarily fail those jobs, including October 26, 29, and 30 from 14:00 to 24:00 UTC.

### What should I replace macos-14 with?

Use `macos-15` for the smallest change, or `macos-26` if you want the newest image. `macos-latest` currently points to `macos-26` but will move again. Pin an explicit version, and check your Xcode selection, because Xcode 15 is not installed on the macOS 15 image.

### Why did my macOS GitHub Actions job fail with no code change?

Most likely a brownout. GitHub intentionally fails `macos-14` jobs during scheduled windows ahead of the November 2 retirement. If the failure lines up with 14:00 to 24:00 UTC on a brownout date, move the job to `macos-15` rather than retrying.

## The decision, concretely

- **Using `macos-14` with the default Xcode?** Pin Xcode 16.2 on `macos-14` first, then switch to `macos-15`.
- **Already on Xcode 16?** Change the label to `macos-15` today and you're done.
- **Using `macos-latest`?** You're already on `macos-26`; pin it explicitly.
- **Org with many repos?** Run the `gh search code` query and open one PR per repo before October 29.

## The call we'd make

Change the label today, not on November 1. Pin `macos-15` and an explicit Xcode version, verify one green build per app, and use the remaining brownouts as free testing rather than outages. The teams that get hurt by image retirements are the ones whose workflows never said which image or which Xcode they depended on.

## Sources

- [GitHub Changelog: macOS 14 runner image retirement](https://github.blog/changelog/2026-10-01-github-actions-macos-14-runner-image-retirement)
- [actions/runner-images: macOS 15 arm64 image README](https://github.com/actions/runner-images/blob/main/images/macos/macos-15-arm64-Readme.md)
- [actions/runner-images: macOS 14 arm64 image README](https://github.com/actions/runner-images/blob/main/images/macos/macos-14-arm64-Readme.md)

