VPSSpark ブログ
← 開発日記に戻る

GitHub Actions の macOS Job が Queued のまま動かない(Xcode が遅いわけではない)

サーバーメモ · Cloud Mac CI #2 · 2026.06.05 · 約 14 分

よくある検索:GitHub Actions macOS runner queued · macos runner stuck in queued · CI queue wait time · self-hosted runner not starting

GitHub Actions の macOS runner queue で Queued 状態のジョブ
macOS runner queued のときは先に wait time を見る。

VPSSpark CI キュー診断スタンダード(クラウド Mac CI シリーズ #2)。読み順:Hook → 公式 → 症状 → Failure Model → Hard Rules → Runbook → FAQ。関連:#1 キャパシティ · #5 スケール失敗事例 · #3 セルフホスト TCO · #8 ビルド高速化。

1 · Hook:直感に反する結論

macOS CI で queued が続くインシデントの多くは、「Mac が足りない」のではなく、PR パスで L2(Archive)が動いていることが原因です。 現場の約 9 割はこのパターンに当てはまります。

GitHub Actions のタイムラインを開くと、赤い PR が「コンパイルが遅い」と報告されがちです。しかし 待ち行列(Queued)実ビルド(In progress) は別メトリクスです。SRE 視点では、まず「待ちが実行を圧倒しているか」を切り分けないと、Xcode バージョンアップや分課の追加は的外れになります。本稿はその切り分け用の規格です。

2 · コア:たった一つの公式

以降の章はすべて、この判定を展開したものです。オンコール Wiki にそのまま貼ってください。

マスター公式

CI queue problem  ⇔  wait time  >>  run time

エンジニアリング上の読み: macOS runner queuedランナープールの飽和 を示し、Xcode ビルドそのものの遅さではありません。

公式が false → 本記事の対象外。Failure 3 Layer Model へ進む前に #8(Xcode / キャッシュ)を確認。true → CI Hard Rules を適用。

日本の iOS チームでは、ホステッド macos-latest と自前ランナーを混在させるケースが多く、「分は余っているのに Queued」 という矛盾が起きやすいです。これは課金単位(minutes)と同時実行枠(concurrency)の混同が根底にあります。公式が true のときだけ、プール設計とトリガー設計に手を入れてください。

3 · 症状:Queued ≠ Running

GitHub Actions では Queued はジョブがワークフローに入ったが まだランナースロットがない 状態です。In progress で checkout、xcodebuild、署名が走ります。1 時間赤い PR を「遅いコンパイル」と見なし、タイムラインを開くと 待ち 52 分・実行 11 分 という典型パターンが出てきます。

よくある誤修正:Xcode を上げる、timeout-minutes を延ばす、GitHub の分を買い足す。Queued 時間は、設定したつもりのタイムアウトに含まれないことが多い ので、タイムアウトだけ伸ばしても待ち行列は解消しません。

計測は ジョブ実行時間queue_wait_seconds と実行時間)で行います。週次で P95 を見ると、金曜のマージラッシュ前に Trigger Explosion を検知できます。

52 min
待ち時間
11 min
実行時間
Queued
≠ In progress
待ち時間 実行時間
UI Queued In progress
ボトルネック GitHub Actions macOS runner queue / self-hosted runner queue Xcode · 署名 · アップロード
誤った対処 分の追加 · 新 Xcode キャッシュ最適化(→ #8)

Slack に「CI が遅い」と流れるとき、まず UI のバッジを確認してください。Queued が長いのに開発者が DerivedData を疑うのは、観測点が実行フェーズに偏っているためです。Runbook では待ちと実行を必ず分けて記録します。

4 · 説明:CI Queue Failure 3 Layer Model

wait time >> run time のとき、原因は次の SRE 型レイヤーのいずれか(または積み重ね)です。

レイヤー 意味 シグナル 今日やること
Capacity Limit プラットフォーム · macos-latest ホステッドのみ macOS runner queued。分 ≠ 同時実行 fanout 削減。Archive をホステッドから外す → #3
Pool Misdesign アーキテクチャ · セルフホスト PR で Archive。fast/archive 共有。1 台占有で全 Queued Hard Rules → #1 #6
Trigger Explosion 負荷 · ワークフロー fanout matrix / paths / 重複 workflow。ジョブ数 > ランナー数 トリガー絞り込み → #5

Capacity Limitホステッド macOS ランナー と組織の macOS 同時実行上限制限)に当たります。Pool Misdesign — 典型根因はジョブ階層 L2 が PR に載っていること、プールの混在です。Trigger Explosion — ハードより YAML を直します。(Failure レイヤー ≠ ジョブ階層 L0/L1/L2。)

3 層は排他ではありません。金曜 17 時に matrix が増え(Trigger)、Archive が fast プールを占有し(Pool)、ホステッド枠も枯れる(Capacity)という「三重苦」は、20 人規模チームの事例記事 #5 でも再現性が高いです。切り分けは上表のシグナル列から始めてください。

5 · 修正:CI Hard Rules(MUST NOT 違反禁止)

Runbook 向けの規範文です。提案ではなく、違反はインシデント扱いにしてください。

CI Hard Rules
Rule 1: PR MUST NOT run L2
                Rule 2: L2 MUST run on isolated pool  (macos-archive)
                Rule 3: fast pool MUST remain unblocked  (fast ≠ archive)

                # Mapping
                PR     → L0 + L1 only     → macos-fast
                main   → L2 only            → macos-archive

ジョブ階層リファレンス(Hard Rules の実装のみ):

階層 作業 プール ルール
L0 モジュールビルド、lint、軽量テスト macos-fast Rule 1 · PR で可
L1 PR 統合、シミュレータテスト macos-fast Rule 1 · Archive MUST NOT
L2 Archive、IPA、TestFlight macos-archive Rules 2+3 · PR MUST NOT

「PR で Archive を一度だけ」は例外に見えても、38 分の L2 が fast ラベルを奪うと、レビュアー全員の L0/L1 が Queued になります。例外運用はプール設計を壊すので、ナイトリー/main 専用ジョブに寄せてください。

図 1 · Pool Misdesign:L2 がスロット占有 → 全 macOS ジョブが queued

PR が macOS ジョブ 6 本発火matrix 未整理
L2 Archive が 1 台で 38 分
L0/L1 がすべて Queuedself-hosted runner queue ブロック
Workflow 例(PR に L2 なし)
jobs:
                  pr-fast:
                    if: github.event_name == 'pull_request'
                    runs-on: [self-hosted, macos-fast]

                  release-archive:
                    if: github.ref == 'refs/heads/main' || github.event_name == 'schedule'
                    runs-on: [self-hosted, macos-archive]

ランナー構成:弾性プール vs 常時オン比較 · #1 サイジング · キュー健全化後 → #3

6 · Runbook:1 ページ(オンコール)

CI Queue Diagnosis · Standard Path
0. CI queue problem ⇔ wait time >> run time  ?  ─No→ #8
                1. Layer: Capacity | Pool Misdesign | Trigger Explosion
                2. MUST: Rule1 PR no L2 · Rule2 L2 isolated · Rule3 fast unblocked
                3. PR workflow has no archive/export/upload ?
                4. wait P95 < 8min → then size runners (#3)

一行まとめ: macOS CI の queued の多くは PR パスの L2 であり、ハード不足ではありません。 macOS runner queued =プール飽和。wait >> run → Failure Model → Hard Rules の順で適用してください。

インシデント後の振り返りでは、「台数を増やした」だけでは再発します。Rule 1〜3 がリポジトリに入っているか、PR テンプレに「Archive ジョブを追加していないか」をチェック項目として載せると再発率が下がります。

7 · FAQ

macOS ランナーはなぜキューしやすいのか

Capacity Limit(同時実行上限)と Pool Misdesign(PR 上の L2)の組み合わせです。まず wait >> run で判定してください。

キュー問題か、遅いビルドか

CI queue problem ⇔ wait time >> run time。true → プール設計。false → #8。

セルフホストでも queued になる理由

Pool Misdesignself-hosted runner queue はラベルとプール構成に従います。Hard Rules 違反はクラウド Mac では直りません。YAML を直してください。

GitHub Actions の分と concurrency の違い

minutes は課金、concurrency は同時スロットです。分を増やしても macOS runner queued は解消しません。

ランナープールの問題かどうか

wait >> run かつ自前 1 台が長時間占有 → Pool Misdesign。ホステッドのみ macos-latest queued → Capacity LimitRunbook 参照。

キューは直ったがビルドがまだ遅い

#8(速度)。サイジング:#1。チーム拡大:#5。

シリーズ(#2 キュー診断)
#1 キャパシティ · #2 本ページ · #3 セルフホスト TCO · #4 Xcode Cloud · #5 20 人事例 · #6 Archive 分離 · #7 プラットフォーム選定 · #8 速度

プール分離の検証:fast プールの PoC が必要ですか

Hard Rules 適用後、ホステッドの GitHub Actions macOS runner queue から L0/L1 を逃がすには、macos-fast ランナーを 1 台立て、wait time P95 が 8 分未満になることを確認してから macos-archive#1 に沿って追加してください。

オフィスネットワークを変えずに 日次 で分離 fast プールを試す場合は クラウド Mac プラン または VPSSpark トップ をご覧ください(トポロジ検証用。ルール遵守は別途必須)。TCO はシリーズ #3。

期間限定

macOS runner queued? まず wait time

GitHub Actions macOS runner queue · CI 診断

ホームへ
期間限定 プランを見る