Сбой сборки GitHub Actions macos-latest не следует лечить повторным запуском или массовым обновлением зависимостей: сначала зафиксируйте фактический образ, архитектуру, Xcode и состояние кеша в журнале Set up job. Если среда изменилась — временно закрепите проверенный runner; если вам постоянно нужны определённый Xcode, долговечный кеш, приватная сеть или непрерывный процесс, перенесите задачу на управляемый удалённый Mac self-hosted runner.
Эта инструкция предназначена для:
- разработчиков, поддерживающих сборку, тестирование и публикацию iOS- или macOS-приложений;
- DevOps-инженеров, выбирающих между GitHub-hosted runner и собственным узлом;
- платформенных команд, которым нужно контролировать подпись, приватные инструменты и кеши.
Последнее обновление: 21 августа 2026 года. Состояние macOS 26, соответствие macos-latest и содержимое образов сверены с официальным объявлением GitHub о миграции образов, перечнем macOS 26 в репозитории runner-images и документацией Apple по выбору Command Line Tools.
Сначала сравните среду, а не код
Ситуация «коммит не менялся, а сборка сегодня упала» ещё не доказывает, что GitHub изменил образ и именно это стало причиной. Возможны как дрейф среды, так и новый путь выполнения, изменившийся секрет, истёкший сертификат или регрессия в самом проекте.
Начните с последнего успешного запуска и первого неуспешного запуска. Для каждого сохраните:
- фактическое значение
runner.os,runner.archиImageOS; - название образа и его версию из блока
Set up job; - выбранный Xcode и путь
xcode-select; - версии Swift, Ruby, Node.js, Python, Homebrew и менеджера зависимостей;
- состояние кеша и ключ, по которому он был восстановлен;
- первый упавший шаг, а не только итоговое сообщение о завершении job.
Сведения о переменных окружения и контексте выполнения сверяйте с документацией GitHub Actions по переменным. Не полагайтесь на отображаемый ярлык macos-latest: он указывает канал образа, но не заменяет доказательство из конкретного запуска.
Разделите результат на две ветви:
- Среда совпадает, а код или входные данные отличаются. Сравните коммиты, lock-файлы, секреты, параметры workflow и артефакты. Дальнейшее расследование относится к проекту.
- Образ, архитектура, Xcode или установленный инструмент отличаются. Не обновляйте всё сразу. Переходите к диагностике источника изменения, иначе вы уничтожите исходное состояние, которое нужно сравнить.
У плавающего образа есть несколько скрытых издержек:
- одинаковый YAML может запускаться с разным набором предустановленных инструментов;
- восстановленный кеш способен содержать бинарники другой архитектуры;
- подпись и доступ к связке ключей зависят не только от компилятора, но и от сессии, сертификатов и профилей;
- повторяемость снижается, когда workflow использует «то, что уже установлено» вместо явной установки версии.
macos-latest и фактический образ: ярлык против журнала
Чтобы понять, какой образ получил GitHub-hosted runner, откройте самый ранний участок лога Set up job. Ищите строки с образом macOS, версией операционной системы, архитектурой и предустановленным программным обеспечением. Затем сопоставьте их с актуальным списком macOS 26 runner-images.
В диагностический шаг удобно добавить явный вывод:
- name: Record runner environment
shell: bash
run: |
set -euxo pipefail
sw_vers
uname -a
uname -m
xcode-select -p
xcodebuild -version
xcrun --sdk macosx --show-sdk-path
ruby --version || true
node --version || true
python3 --version || true
brew --prefix || true
Команды не исправляют проблему — они создают снимок, который можно сравнить с успешным запуском. Для iOS-проекта дополнительно выведите доступные SDK и симуляторы:
xcodebuild -showsdks
xcrun simctl list runtimes
Если в логе обнаружен macOS 26, это ещё не означает, что любой проект обязан работать на нём без изменений. Сверяйте требования конкретной версии Xcode и поддерживаемые SDK с официальной документацией Apple по настройке инструментов Xcode. Список runner-images — это описание текущего образа, а не бессрочная гарантия его состава.
GitHub заранее публикует изменения каналов образов. Например, объявление GitHub от 14 мая 2026 года о предстоящих миграциях нужно учитывать при разборе даты появления сбоя. Но совпадение даты и ошибки — только корреляция. Причину подтверждает лишь сравнение журнала, команды выбора Xcode и результата повторной сборки.
ARM против Intel: бинарник, пакет и кеш должны совпадать
Переход между ARM и Intel проявляется не всегда в первой строке ошибки. Частые признаки — bad CPU type in executable, невозможность загрузить расширение, падение нативного Node-модуля, ошибка Ruby Gem или линковка с библиотекой, собранной под другую архитектуру.
Проверяйте цепочку из трёх уровней:
uname -m
file path/to/binary
brew --prefix
uname -m показывает архитектуру текущего процесса. file помогает увидеть архитектуры конкретного исполняемого файла или библиотеки. brew --prefix показывает, откуда загружаются пакеты Homebrew. Если эти сведения не согласуются с содержимым кеша, ярлык runner сам по себе ничего не доказывает.
Особенно внимательно проверяйте:
- Ruby Gem с нативными расширениями;
- пакеты Homebrew, от которых зависят OpenSSL или другие библиотеки;
- Node.js-модули с предварительно собранными бинарниками;
- Swift Package Manager и сторонние XCFramework;
- кеши DerivedData, Pods, Carthage или промежуточных артефактов;
- скрипты, которые выбирают пакет по архитектуре машины.
Выбор между ARM и Intel делайте по зависимости, а не по предположению, что «новее всегда лучше».
- Фиксируйте архитектуру, если проект использует закрытый бинарный SDK, плагин или инструмент, доступный только в одной сборке.
- Пересобирайте зависимости и кеши, если обе архитектуры поддерживаются, но старые артефакты были перенесены между средами.
- Не смешивайте кеши ARM и Intel одним ключом.
- Если переход неизбежен, проведите отдельный прогон без кеша, затем создайте новый кеш с архитектурой и версией инструментария в ключе.
Пример более безопасного ключа:
key: >
${{ runner.os }}-${{ runner.arch }}-
xcode-${{ matrix.xcode }}-
${{ hashFiles('**/Package.resolved', '**/Podfile.lock') }}
Формат ключа зависит от вашего workflow, но логика обязательна: операционная система, архитектура, Xcode и lock-файлы должны участвовать в идентификации артефакта. Возможности и ограничения кеширования сверяйте с документацией GitHub по dependency caching.
Xcode: плавающий выбор против явно заданного инструментария
Если сборка началась падать после изменения образа, проверьте три разные ситуации:
- требуемого Xcode действительно нет в образе;
- Xcode присутствует, но выбран другой Developer Directory;
- Xcode выбран правильно, однако проект или зависимость ещё не совместимы с новым SDK.
Не объединяйте их в одну ошибку «Xcode сломан». Сначала выведите доступные приложения и текущий выбор:
ls -1 /Applications | grep -i Xcode || true
xcode-select -p
xcodebuild -version
Когда нужная версия установлена, выбирайте её явно:
sudo xcode-select -s /Applications/Xcode_XX.X.app
xcodebuild -runFirstLaunch
xcodebuild -version
Вместо XX.X подставьте версию, которую вы проверили в официальном списке образа и в требованиях проекта. Не оставляйте в production workflow путь, существование которого не подтверждается текущим образом.
Второй вариант — закрепить runner-метку, на которой уже прошли контрольные проверки. Это временная мера: она уменьшает риск следующего автоматического перехода, но не заменяет установку зависимостей и проверку Xcode внутри workflow. Когда образ перестаёт соответствовать требованиям или нужная версия удаляется, фиксированная метка лишь переносит проблему на более позднюю дату.
Надёжнее разделить этапы:
- явно выбрать Xcode;
- проверить
xcodebuild -versionдо установки зависимостей; - вывести SDK и simulator runtime;
- выполнить чистую сборку;
- только после этого разрешить кеширование;
- отдельно проверить архив и подпись.
Так вы отличите отсутствие инструмента от несовместимости проекта. Вторая причина часто скрывается за сообщением компилятора, тогда как первая подтверждается списком /Applications и ошибкой выбора Developer Directory.
Предустановленные пакеты против воспроизводимой установки
Ruby, Node.js, Python, Homebrew, OpenSSL и системные утилиты могут измениться вместе с образом. Даже если основной Xcode остался прежним, установочный скрипт может получить другой путь к библиотеке, иной параметр компилятора или новый формат вывода команды.
Проверьте в логе:
ruby --version
gem env
node --version
npm --version
python3 --version
brew config
openssl version
Затем найдите, какие из этих версий реально используются сборкой, а какие просто присутствуют в системе. Наличие пакета в образе не означает, что он является частью стабильного контракта вашего проекта.
В workflow стоит явно:
- выбрать версию Ruby через проверенный менеджер;
- использовать lock-файл для Gem и JavaScript-зависимостей;
- устанавливать нужную версию Node.js и Python, а не принимать системную;
- фиксировать формулу Homebrew или заменять её артефактом с контролируемой версией;
- проверять пути заголовков и библиотек OpenSSL;
- очищать кеш после изменения архитектуры или Xcode.
Особенно опасна последовательность «восстановить старый кеш — установить новые пакеты — собрать». Она может скрыть несовместимость до этапа линковки. Для контрольного запуска отключите кеш полностью. Если чистая сборка проходит, а кешированная падает, причина находится в ключе, содержимом или порядке восстановления кеша.
Подпись и симулятор: отдельный слой отказа
Успешная компиляция не означает, что публикационный pipeline восстановлен. Архивирование, подписание, загрузка и запуск тестов на симуляторе имеют собственные точки отказа.
Архив собирается, но подпись не проходит
Соберите сведения о:
- выбранной схеме и конфигурации;
- идентификаторе команды и bundle identifier;
- доступных сертификатах;
- профиле provisioning;
- состоянии временной связки ключей;
- правах процесса, выполняющего
xcodebuild.
Секреты, пароли и содержимое сертификатов не выводите в лог. Используйте только заполнители вроде ${{ secrets.SIGNING_PASSWORD }}. Если workflow создаёт временную keychain, проверьте её создание, разблокировку, импорт сертификата и удаление после job. Ошибка после смены образа может быть связана с разрешениями или способом работы с keychain, а не с исходным кодом.
Компиляция проходит, но тесты не видят симулятор
До тестов сохраните вывод:
xcodebuild -showsdks
xcrun simctl list devices
xcrun simctl list runtimes
Сопоставьте destination с реально установленным runtime. Не подставляйте идентификатор устройства, который существовал в старом образе. Если runtime отсутствует, это проблема состава среды; если runtime есть, но устройство не запускается, исследуйте состояние CoreSimulator и параметры job.
Командная сборка работает, а интерактивный шаг — нет
GitHub-hosted runner выполняет команды в автоматизированной сессии. Скрипт, рассчитывающий на открытый графический сеанс, ручное подтверждение или сохранённое состояние пользователя, может пройти локально и упасть в CI. Отдельно проверяйте доступ к keychain, переменные окружения и команды, требующие GUI.
Повторный прогон должен включать чистую компиляцию, кешированную компиляцию, тесты, архивирование и подпись. Публикацию не считайте восстановленной, пока не проверена вся цепочка.
План восстановления: контрольные точки от журнала до production
Используйте последовательность ниже, не перескакивая к миграции узла после первой ошибки.
Этап 1. Зафиксировать исходные данные
Сохраните успешный и неуспешный логи, commit SHA, lock-файлы, параметры matrix, секреты по именам и ключ кеша. Выпишите первое отличие, а не все последующие ошибки.
Этап 2. Повторить без изменений
Запустите тот же commit с тем же workflow. Цель — понять, воспроизводится ли сбой. Не меняйте одновременно Xcode, кеш, зависимости и runner-метку: иначе результат невозможно интерпретировать.
Этап 3. Получить снимок среды
Добавьте вывод sw_vers, uname -m, xcode-select, xcodebuild, SDK, симуляторов и версий менеджеров пакетов. Сопоставьте его с официальным образом и предыдущим успешным запуском.
Этап 4. Изолировать архитектуру
Запустите чистую установку зависимостей без старого кеша. Проверьте бинарники через file, пути Homebrew и архитектуру процесса. После успешного прогона создайте новый архитектурный кеш.
Этап 5. Изолировать Xcode
Явно выберите проверенную версию и повторите чистую сборку. Если нужного Xcode нет, закрепите совместимый образ только как краткосрочное восстановление и запланируйте перенос проверки версии внутрь workflow.
Этап 6. Проверить подпись и симуляторы
Отдельными шагами проверьте keychain, сертификаты, provisioning, simulator runtime и destination. Не объединяйте диагностику подписи с диагностикой компилятора в один нечитаемый скрипт.
Этап 7. Прогнать четыре сценария
Перед возвратом в production выполните:
- чистую сборку без кеша;
- сборку с новым кешем;
- тесты на требуемом simulator runtime;
- архивирование и подпись.
Затем перезапустите узел или исполнитель и повторите критический сценарий. Это особенно важно для self-hosted runner: состояние после перезагрузки должно быть таким же, как до неё.
Для текущего workflow отметьте результаты чек-листом:
- [ ] В
Set up jobзаписаны образ, версия macOS и архитектура. - [ ] Снимок среды сопоставлен с последним успешным запуском.
- [ ] Xcode выбран явно, а версия подтверждена командой
xcodebuild -version. - [ ] Кеш разделён по ОС, архитектуре, Xcode и lock-файлам.
- [ ] Чистая установка зависимостей прошла без старых артефактов.
- [ ] Доступные SDK и simulator runtime соответствуют destination.
- [ ] Временная keychain создаётся и разблокируется без вывода секретов.
- [ ] Проверены компиляция, кешированная сборка, тесты, архив и подпись.
- [ ] После перезапуска runner критический сценарий повторён успешно.
Фиксированный runner против удалённого Mac: решение по ограничениям
Фиксированная метка подходит для временного восстановления, когда сбой появился недавно, задача не хранит состояние, а проект можно полностью подготовить внутри job. Этот путь требует последующей проверки: образ может измениться, а нужный Xcode — исчезнуть из доступного набора.
Управляемый удалённый Mac self-hosted runner оправдан, если одновременно важны несколько условий:
- фиксированный Xcode должен оставаться доступным между запусками;
- кеш DerivedData или зависимостей велик и его нерационально создавать заново;
- сборке нужен приватный сервис, внутренний репозиторий или закрытая сеть;
- подпись требует контролируемого доступа к физическому узлу и keychain;
- pipeline должен оставаться доступным между job, а не начинаться с чистой среды;
- платформа отвечает за обслуживание, обновления и аудит самого runner.
При этом собственный узел не отменяет инженерную дисциплину. На нём нужно закрепить версии, ограничить права, вести журнал изменений, очищать секреты, настроить перезапуск и проверить восстановление после сбоя питания или перезагрузки.
Если вам нужен именно такой изолированный этап проверки, аренда удалённого Mac для тестового runner позволяет сначала прогнать реальный workflow, а не переносить production вслепую. Для сравнения вариантов оплаты можно открыть условия аренды Mac, но решение принимайте после проверки Xcode, кеша, подписи и приватной сети на вашем проекте.
В документации GitHub по hosted runners отдельно учитывайте границы среды GitHub-hosted runner. Более крупные варианты имеют собственные характеристики и правила, поэтому нельзя переносить выводы о стандартном runner на любой другой тип исполнителя; доступные параметры проверяйте по официальному описанию larger runners.
На практике текущий плавающий вариант проигрывает управляемому удалённому Mac там, где нужны постоянный кеш, стабильный Xcode и доступ к внутренним ресурсам. У GitHub-hosted runner также нет гарантии, что следующий запуск будет иметь тот же локальный диск и тот же набор предустановленных инструментов. Поэтому разумный переход — не заменить всю систему в день сбоя, а сначала воспроизвести workflow на отдельном удалённом Mac, пережить перезапуск узла и сравнить все четыре контрольных сценария. Если результаты стабильны, перенос production будет техническим решением, а не реакцией на единичную ошибку.