В документации GitHub указано: если в runs-on перечислено несколько меток, self-hosted runner должен соответствовать каждой из них, а не только одной — это подтверждается в официальном описании выбора Runner для Job. Поэтому при сообщении «GitHub Actions Mac Runner всё время в очереди» не следует сразу добавлять Mac-узлы.
План действий такой: сначала зафиксируйте runs-on, Runner Group и состояние узлов; затем отделите отсутствие подходящего Runner от занятого узла и сбоя службы; после исправления выполните минимальную задачу, реальную сборку Xcode и производственную проверку. Только если маршрутизация и сервис исправны, а очередь стабильно сохраняется, имеет смысл увеличивать ёмкость удалённых Mac или разделять пул задач.
Эта инструкция предназначена для мобильных разработчиков, которые запускают Xcode-сборки на self-hosted Mac Runner и видят длительное состояние queued. Она также полезна DevOps-инженерам, отвечающим за метки, Runner Group, права репозитория и постоянную работу macOS-службы, а также руководителям платформы, которым нужно решить: чинить существующий узел, регистрировать его заново или расширять пул.
Сначала зафиксируйте источник ожидания
Слово queued описывает результат, но не обязательно причину. До изменения YAML, удаления Runner или перезапуска службы нужно сохранить снимок доказательств:
- URL запуска Workflow и конкретного Job;
- текст
runs-onсо всеми метками; - комментарий или сообщение в интерфейсе Job;
- состояние подходящих Runner:
Idle,Busy,Offline; - принадлежность узла к Runner Group;
- разрешение группы для нужного репозитория;
- активные Job и последние строки диагностического журнала.
Официальная справка по маршрутизации self-hosted Runner разделяет соответствие меткам, доступность узла и его занятость. Это важное различие: Runner может отображаться в административном интерфейсе, но не быть допустимым исполнителем конкретного Job.
Проверка должна идти в таком порядке:
- Условия самого Workflow. Job может ждать предыдущий Job через
needs, ручное одобрение окружения, ограничение concurrency или иное условие запуска. - Права и маршрутизация. Ни один доступный узел не подходит одновременно по меткам и Runner Group.
- Ёмкость. Подходящие Mac Runner действительно заняты активными задачами.
- Служба узла. Mac виден как доступный или недавно был доступен, но процесс Runner не поддерживает рабочее соединение.
GitHub отдельно описывает ограничения concurrency для Workflow. Поэтому изменение количества Mac не устранит очередь, если новый запуск ожидает освобождения группы concurrency или завершения зависимости.
Важно. Не меняйте одновременно YAML, набор меток, права Runner Group и службу launchd. Иначе после исчезновения очереди будет невозможно установить, что именно помогло, а возврат к прежнему состоянию станет менее безопасным.
Диагностика runs-on и меток
Когда метки совпадают не полностью
Самая частая ошибка в маршрутизации — сравнение одной ожидаемой метки с одной фактической. В рабочем файле может быть, например:
jobs:
ios-build:
runs-on: [self-hosted, macOS, arm64, xcode-ci]
steps:
- uses: actions/checkout@v4
- run: xcodebuild -version
В этом примере Runner должен иметь все четыре метки. Если после пересоздания узлу назначили только self-hosted, macOS и arm64, Job останется в очереди, хотя интерфейс будет показывать доступный Mac.
Проверьте:
- регистр букв в пользовательских метках;
- дефисы, подчёркивания и пробелы;
- наличие архитектурной метки;
- метку нужного инструментария, например
xcode-ci; - не была ли пользовательская метка удалена после повторной регистрации;
- совпадает ли репозиторий с тем, где реально зарегистрирован Runner.
Официальная документация по меткам self-hosted Runner подтверждает, что метки участвуют в выборе узла, а не служат только описанием. На время диагностики полезно создать отдельный Job с узким, но проверяемым набором требований:
name: runner-route-check
on:
workflow_dispatch:
jobs:
route:
runs-on: [self-hosted, macOS, arm64]
steps:
- name: Show execution context
run: |
uname -a
sw_vers
echo "RUNNER_NAME=$RUNNER_NAME"
echo "RUNNER_OS=$RUNNER_OS"
echo "RUNNER_ARCH=$RUNNER_ARCH"
Этот Job не должен становиться постоянной заменой производственных ограничений. Если он запускается, а вариант с xcode-ci остаётся в очереди, проблема почти наверняка находится в пользовательской метке или в другом ограничении маршрутизации.
Почему runs-on совпадает, но Job остаётся queued
Совпадение текста метки не доказывает, что Job может использовать узел. Нужно одновременно подтвердить:
- Runner принадлежит правильному уровню — репозиторию, организации или предприятию;
- Runner Group разрешена целевому репозиторию;
- узел не занят другим Job;
- сервис Runner действительно подключён к GitHub;
- Workflow не заблокирован предварительным условием.
В рабочем файле следует временно убрать только необязательные требования, а не расширять выбор до общего macOS или единственного self-hosted:
runs-on: [self-hosted, macOS, arm64]
Если упрощённый Job проходит, это свидетельствует о проблеме в удалённой метке, но не подтверждает готовность узла к Xcode-сборке. После диагностики верните производственные требования и сохраните разницу между тестовым и рабочим запуском.
Runner Group и границы доступа
Почему Runner Group недоступна выбранному репозиторию
Метки отвечают на вопрос «какой узел подходит», а Runner Group — на вопрос «имеет ли этот репозиторий право его использовать». Эти проверки не заменяют друг друга. Официальное руководство по управлению Runner Group рекомендует проверять область действия группы и список разрешённых репозиториев.
Порядок проверки:
- Откройте уровень, на котором создан Runner: репозиторий, организация или предприятие.
- Найдите группу, к которой относится Mac.
- Зафиксируйте список разрешённых репозиториев и политики организации.
- Проверьте, не перемещён ли Runner в другую группу после обслуживания.
- Сопоставьте целевой репозиторий с фактическим разрешением группы.
- Запустите Workflow без секретов и без публикации артефактов, чтобы проверить только видимость маршрута.
Для временной проверки допустим отдельный диагностический Workflow с командами uname и sw_vers, но не следует использовать в нём сертификаты подписи, ключи публикации или доступ к рабочим секретам. Если после исправления группы тестовый Job становится назначаемым, зафиксируйте изменение политики до перехода к боевой сборке.
Узкая группа обычно безопаснее широкой: она снижает риск, что рабочий Workflow случайно попадёт на Mac с неподходящей версией Xcode, другим профилем подписи или незапланированным набором секретов. При этом чрезмерно узкая политика может создать очередь без единого сбоя на самом Mac.
Состояние Idle, занятость и зависшие процессы
Почему Mac Runner отображается как Idle, но не выполняет Workflow
Статус Idle нужно сопоставить с конкретным Job и временем его постановки в очередь. Он не заменяет проверку меток, группы и подключения процесса. Если Job не видит допустимого маршрута, свободный Runner останется Idle, потому что GitHub не назначает ему неподходящую задачу.
Если маршрутизация подтверждена, проверьте сам узел через SSH:
ps aux | egrep 'Runner.Listener|Runner.Worker|xcodebuild|simctl' | grep -v grep
df -h /
uptime
Команды показывают наличие процессов и общую доступность системы, но не являются доказательством корректного подключения к GitHub. Для этого нужны журналы Runner и состояние службы. Учитывайте следующие источники реальной занятости:
- длительный
xcodebuild, который не завершился после ошибки теста; - Simulator, оставшийся после UI-тестов;
- процесс подписи или публикации, удерживающий рабочий каталог;
- зависший дочерний процесс, из-за которого Worker не завершает Job;
- параллельная задача, не отражённая в локальном предположении инженера.
Не завершайте процессы по маске без сохранения PID, журнала и идентификатора Job. Принудительное удаление xcodebuild может оставить временные файлы, Simulator или рабочее дерево в непредсказуемом состоянии. Сначала установите, что задача действительно завершена на стороне GitHub, затем остановите подтверждённый зависший процесс и повторите минимальную проверку.
macOS-служба и получение задачи
Когда self-hosted Runner онлайн, но не принимает Job
SSH-доступ означает только то, что удалённый Mac принимает сетевое соединение. Это не доказывает, что Runner Listener запущен от правильной учётной записи, имеет доступ к рабочему каталогу и восстановил соединение после перезагрузки.
На macOS проверьте зарегистрированные службы:
launchctl list | grep -i runner
launchctl print system/com.example.runner
Имя com.example.runner в примере является условным: используйте фактическую метку службы, созданную при настройке узла. Затем проверьте журнал:
log show --last boot --style compact \
--predicate 'process CONTAINS[c] "Runner"'
Синтаксис и диагностические направления следует сверять с официальной документацией GitHub по мониторингу macOS Runner. В журнале ищите не только ошибки сети, но и:
- запуск службы от другой учётной записи;
- отсутствие прав на рабочий каталог;
- несуществующий путь после переноса каталога;
- разрыв соединения и отсутствие повторного подключения;
- повреждённое состояние регистрации;
- перезапуск после обновления или перезагрузки Mac.
Порядок низкорискового восстановления:
- Сохраните журналы и текущий файл конфигурации службы.
- Проверьте владельца рабочего каталога и доступ сервисной учётной записи.
- Перезапустите только службу, не удаляя регистрацию Runner.
- Дождитесь появления нового подключения в административном интерфейсе.
- Запустите минимальный Job с целевыми метками.
- Проверьте реальную Xcode-команду и возврат результата.
Переустановка службы или повторная регистрация нужны только тогда, когда журнал показывает повреждённую регистрацию либо некорректный сервисный путь. Удаление Runner отзывает его участие в маршрутизации; перед такой операцией сохраните конфигурацию, рабочие каталоги и способ получить новый регистрационный токен. Инструкция GitHub по удалению Runner описывает операцию как отдельное изменение жизненного цикла, а не как обычную перезагрузку.
Чек-лист безопасного восстановления
Перед расширением пула выполните пункты по порядку:
- [ ] Сохранен URL queued Job и его комментарий о состоянии.
- [ ] Зафиксирован исходный
runs-onбез предварительного расширения меток. - [ ] Сверены все фактические метки Mac Runner, включая архитектуру и пользовательские требования.
- [ ] Проверена принадлежность узла к нужному Runner Group.
- [ ] Подтверждено, что целевой репозиторий разрешён в политике группы.
- [ ] Исключены
needs, ручное одобрение окружения и concurrency как независимые причины ожидания. - [ ] Сопоставлены активные Job с процессами
xcodebuild, Simulator и публикации. - [ ] Сохранены диагностические журналы Runner до перезапуска.
- [ ] Проверено состояние
launchdи рабочий каталог сервисной учётной записи. - [ ] Выполнен минимальный Job с требуемыми метками.
- [ ] Выполнена настоящая Xcode-сборка с возвратом артефакта или отчёта.
- [ ] Зафиксировано, что очередь исчезла из-за маршрутизации, восстановления службы или изменения ёмкости.
Если требуется отдельное окружение для такой проверки, сначала изучите описание удалённого Mac для инженерных задач, а не переносите неисправный производственный узел в неопределённое состояние.
Матрица решений перед изменением инфраструктуры
Ниже приведены три таблицы, которые удобно заполнить после сбора журналов. Они не заменяют проверку в GitHub, но помогают не смешивать разные классы неисправностей.
| Наблюдение | Проверяемый источник | Вероятный класс проблемы | Первое действие |
|---|---|---|---|
Runner Idle, Job не назначается |
runs-on, список меток, комментарий Job |
Нет полного совпадения меток | Исправить метку или Workflow, затем запустить диагностический Job |
| Метки совпадают, репозиторий не видит группу | Настройки Runner Group | Ограничение доступа | Исправить разрешение группы, не менять число Mac |
Подходящий Runner Busy |
Активные Job и локальные процессы | Реальная занятость или зависание | Найти владельца задачи, затем безопасно освободить узел |
| Runner виден, Listener не подключается | Журналы и launchd |
Сбой macOS-службы | Восстановить сервис и проверить автозапуск |
| Все проверки успешны, очередь возвращается | История запусков и длительность задач | Недостаточная ёмкость или плохое разделение задач | Разделить пул либо добавить Mac после подтверждения нагрузки |
| Результат проверки | Решение | Что не следует делать |
|---|---|---|
Ошибка только в runs-on |
Исправить YAML или пользовательскую метку | Не оставлять широкую метку в production |
| Ошибка в Runner Group | Изменить разрешённый список репозиториев | Не открывать группу всей организации без необходимости |
| Узел занят незавершённым процессом | Очистить подтверждённый процесс и повторить Job | Не убивать все процессы Xcode по общей маске |
| Регистрация исправна, служба не запущена | Восстановить launchd, сохранив конфигурацию |
Не удалять Runner до сохранения доказательств |
| Один узел стабильно выполняет тест, но производственные Job ждут | Создать отдельный пул или увеличить ёмкость | Не считать любую очередь признаком нехватки Mac |
| Проверочный запуск | Что подтверждает | Критерий перехода дальше |
|---|---|---|
Минимальная команда uname и sw_vers |
Маршрутизацию и базовое выполнение | Job назначен правильному узлу |
xcodebuild -version |
Наличие ожидаемого инструментария | Версия и путь соответствуют требованиям проекта |
| Реальная сборка без публикации | Исполнение CI-цепочки | Сборка завершается, логи возвращаются |
| Производственный аналог с целевыми метками | Полный маршрут и права | Результат воспроизводим без ручного входа |
| Повторный запуск после перезагрузки службы | Восстановление постоянного узла | Runner снова принимает задачу после восстановления |
Когда нужна дополнительная ёмкость Mac
Добавление удалённых Mac оправдано только после того, как:
- метки полностью совпадают;
- Runner Group разрешена репозиторию;
- concurrency и зависимости не блокируют запуск;
- служба стабильно подключается;
- активные задачи действительно занимают все подходящие узлы;
- повторная производственная проверка подтверждает именно очередь мощности, а не отказ маршрутизации.
В остальных случаях новый Mac просто увеличит количество доступных, но непригодных или недоступных исполнителей. Для Xcode-сборок полезнее разделить задачи по назначению: быстрые проверки, Simulator-тесты, подписание и длительные публикации могут иметь разные метки и группы. Такое разделение уменьшает риск, что один долгий процесс заблокирует весь общий пул, но требует отдельной проверки прав и поддержания одинаковой политики образов.
Если текущий узел нельзя безопасно менять во время релиза, изолированный удалённый Mac может выступить временным контрольным исполнителем: на нём проверяют тот же Workflow, те же метки и тот же способ получения результата. После такого сравнения становится понятнее, сломан ли исходный Runner или проблема находится на уровне GitHub-маршрута.
Существующая схема на локальном Mac или Linux-сервере часто проигрывает в этой задаче по трём причинам: рабочая машина может выключиться, доступ к ней зависит от локальной сети, а Linux не заменяет macOS-инструменты и Apple-цепочку подписи. Покупка отдельного Mac, напротив, требует заранее оплаченного оборудования и самостоятельного контроля постоянной доступности. Если нужна временная изолированная среда для повторения очереди, резервный узел или проверка восстановления службы, аренда удалённого Mac у NodeMini позволяет начать с ограниченного периода, не превращая диагностический эксперимент в немедленную закупку оборудования. Условия доступа и порядок подготовки можно сверить в справочном центре NodeMini.
Итоговое правило для ситуации «GitHub Actions Mac Runner всё время в очереди» простое: сначала докажите, что Job видит подходящий Runner по всем меткам и имеет право использовать его группу; затем подтвердите реальную занятость и исправность macOS-службы. Только после успешной повторной проверки, когда очередь сохраняется именно из-за занятых подходящих узлов, выбирайте разделение задач или дополнительную аренду Mac как управляемый способ расширения CI.